Flutter로 앱 개발 입문하기
양수장
GDG Songdo Organizer (2020~ )
Flutter Songdo Organizer (2022 ~ )
Tech Lead @bluefrog (2020 ~ )
- Android
- Flutter
- Back-end + DevOps
I’m an App Developer.
Bio
https://github.com/yangster-chief
yangsterchief@duck.com
양수장 (Bruce)
본 발표는 Flutter 환경설정 같은 기초적인 내용부터 앱 개발에 필요한 Android, iOS
기초지식을 30분에 걸쳐 다룹니다.
시간 관계상 많은 내용이 생략될 수 있으나, 관련 내용을 추후 찾아보실수 있도록
링크를 첨부하겠습니다.
이 발표로 앱 개발에 많은 도움이 되었으면 좋겠습니다.
Intro
Flutter가 몸에 좋은 이유
Flutter가 몸에 좋은 이유
Flutter Korea
Flutter Seoul (서울, 한국(대한민국)) | Meetup
Flutter Songdo (인천, 한국(대한민국)) | Meetup
Flutter Daegu - 플러터 대구 (대구, 한국(대한민국)) | Meetup
Flutter Forward
Flutter at Google I/O 2022
Flutter Update: Windows
Flutter가 몸에 좋은 이유
Flutter가 몸에 좋은 이유
아니 진짜 이렇게까지하는데
아직도 플러터 안하는
사람은 없겠죠 진짜 이젠
찍먹해야 되는거 아니냐고요
Flutter is …
Flutter is an open source framework by Google
for building beautiful, natively compiled,
multi-platform applications from single codebase.
- Framework
- UI 구성요소 및 위젯
- Engine
- 렌더링 엔진
- Embedder
- 엔진을 구동하기 위한 네이티브 코드
Flutter Architectural Layers
- Flutter 설치 및 환경설정 ( 링크 )
- Android 시뮬레이터 설치 ( 링크 )
- iOS 시뮬레이터 설치 ( 링크 )
- Android Studio IDE 환경설정 ( 링크 )
- Visual Studio Code IDE 환경설정 ( 링크 )
Setting up Development Environment
Flutter Project Overview
...
<dict>
...
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
...
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>sms</string>
<string>tel</string>
<string>mailto</string>
</array>
...
<key>NSCameraUsageDescription</key>
<string>카메라에 접근하도록 허용합니다.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>위치 접근 권한이 필요합니다.</string>
...
</dict>
</plist>
/ios/Runner/Info.plist
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<queries>
<!-- If your app checks for SMS support -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="sms" />
</intent>
...
</queries>
<application
android:label="@string/app_name"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"
...
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
...
</activity>
...
</application>
</manifest>
/Android/app/src/main/AndroidManifest.xml
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
BUNDLE_IDENTIFIER=com.example
FLUTTER_TARGET=lib/src/config/env/env_develop.dart
DISPLAY_NAME=flutter_example
GOOGLE_SERVICE_INFO_PLIST=Develop/GoogleService-Dev-Info.plist
FIREBASE_APP_ID_FILE=firebase_app_id_file_dev.json
/ios/Flutter/Develop.xcconfig
...
android {
...
defaultConfig {
applicationId "com.example"
minSdkVersion 23
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
dimension "build-type"
}
signingConfigs {
release {
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
}
}
...
flavorDimensions "build-type"
productFlavors {
...
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
/Android/app/build.gradle
name: flutter_example
...
environment:
sdk: ">=2.17.3 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: "^1.0.2"
device_preview: "^1.1.0"
...
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: "^2.0.1"
...
flutter:
uses-material-design: true
assets:
- assets/icons/
- assets/placeholder/
- assets/images/
...
/pubspec.yaml
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
private val openIntent: String = "openIntentChannel"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor,
openIntent).setMethodCallHandler(
IndentHandler(activity)
)
}
}
/Android/app/src/main/kotlin/MainActivity.kt
import UIKit
import Flutter
import FirebaseCore
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions:
launchOptions)
}
private func setupFirebase() {
...
}
}
/ios/Runner/AppDelegate.swift
- Flutter에서는 모든 것이 위젯입니다.
- Stateless Widget & Stateful Widget
- Layout Widget ( 링크 )
- Scaffold Widget ( 링크 )
Flutter Widgets
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(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
/lib/main.dart
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(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
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),
));
}
}
/lib/main.dart
- Flutter에서 실행할 수 없는 코드를 네이티브에서 실행
- Method Channel
- Event Channel
Platform Channel
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('samples.flutter.dev/battery');
// Get battery level.
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
...
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// This method is invoked on the main thread.
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
}
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// This method is invoked on the UI thread.
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Platform Channel
Packages and Plugins (The Boring Flutter Development Show, Ep. 6)
Tips & Best Practices
[Flutter Festival GDG Songdo] GetX, provider, bloc 패턴 비교 분석 - 유병욱
Tips & Best Practices
[Flutter Festival GDG Songdo] Flutter에 Clean Architecture를 얹어보자 - 양수장
Tips & Best Practices
[Flutter Festival GDG Songdo] Flutter 상용화 앱 프로젝트 적용기 - 손민재
Tips & Best Practices
DevFest Songdo 2022 - Flutter Theme 디자이너와 호감작하기
Tips & Best Practices
Github Actions를 활용한 Flutter 배포 자동화하기 - 양수장(GDG Songdo) I 모두콘 2022
Tips & Best Practices
Flutter로 앱, 스토어 정보, 스크린샷 모두를 90개 언어로 자동 번역할 수 있다? - 이준규(GDG Songdo) I
모두콘 2022
Tips & Best Practices
Flutter 앱에서 머신러닝 활용 방법 - 박제창(Flutter Seoul) I 모두콘 2022
Tips & Best Practices
Flutter, 어떻게 해야 더 잘 쓸까? - 유병욱(Flutter Seoul) I 모두콘 2022
THANK YOU!
https://github.com/yangster-chief
yangsterchief@duck.com
양수장 (Bruce)

Flutter Forward EXTENDED - Flutter로 앱 개발 입문하기

  • 1.
    Flutter로 앱 개발입문하기 양수장
  • 2.
    GDG Songdo Organizer(2020~ ) Flutter Songdo Organizer (2022 ~ ) Tech Lead @bluefrog (2020 ~ ) - Android - Flutter - Back-end + DevOps I’m an App Developer. Bio https://github.com/yangster-chief yangsterchief@duck.com 양수장 (Bruce)
  • 3.
    본 발표는 Flutter환경설정 같은 기초적인 내용부터 앱 개발에 필요한 Android, iOS 기초지식을 30분에 걸쳐 다룹니다. 시간 관계상 많은 내용이 생략될 수 있으나, 관련 내용을 추후 찾아보실수 있도록 링크를 첨부하겠습니다. 이 발표로 앱 개발에 많은 도움이 되었으면 좋겠습니다. Intro
  • 4.
  • 5.
  • 7.
    Flutter Korea Flutter Seoul(서울, 한국(대한민국)) | Meetup Flutter Songdo (인천, 한국(대한민국)) | Meetup Flutter Daegu - 플러터 대구 (대구, 한국(대한민국)) | Meetup
  • 8.
    Flutter Forward Flutter atGoogle I/O 2022 Flutter Update: Windows Flutter가 몸에 좋은 이유
  • 9.
  • 10.
    아니 진짜 이렇게까지하는데 아직도플러터 안하는 사람은 없겠죠 진짜 이젠 찍먹해야 되는거 아니냐고요
  • 11.
    Flutter is … Flutteris an open source framework by Google for building beautiful, natively compiled, multi-platform applications from single codebase.
  • 12.
    - Framework - UI구성요소 및 위젯 - Engine - 렌더링 엔진 - Embedder - 엔진을 구동하기 위한 네이티브 코드 Flutter Architectural Layers
  • 13.
    - Flutter 설치및 환경설정 ( 링크 ) - Android 시뮬레이터 설치 ( 링크 ) - iOS 시뮬레이터 설치 ( 링크 ) - Android Studio IDE 환경설정 ( 링크 ) - Visual Studio Code IDE 환경설정 ( 링크 ) Setting up Development Environment
  • 14.
  • 15.
  • 16.
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example"> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <queries> <!-- Ifyour app checks for SMS support --> <intent> <action android:name="android.intent.action.VIEW" /> <data android:scheme="sms" /> </intent> ... </queries> <application android:label="@string/app_name" android:name="${applicationName}" android:icon="@mipmap/launcher_icon"> <activity android:name=".MainActivity" android:exported="true" ... android:windowSoftInputMode="adjustResize"> <!-- Specifies an Android theme to apply to this Activity as soon as the Android process has started. This theme is visible to the user while the Flutter UI initializes. After that, this theme continues to determine the Window background behind the Flutter UI. --> ... </activity> ... </application> </manifest> /Android/app/src/main/AndroidManifest.xml
  • 17.
    // Configuration settingsfile format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 BUNDLE_IDENTIFIER=com.example FLUTTER_TARGET=lib/src/config/env/env_develop.dart DISPLAY_NAME=flutter_example GOOGLE_SERVICE_INFO_PLIST=Develop/GoogleService-Dev-Info.plist FIREBASE_APP_ID_FILE=firebase_app_id_file_dev.json /ios/Flutter/Develop.xcconfig
  • 18.
    ... android { ... defaultConfig { applicationId"com.example" minSdkVersion 23 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName dimension "build-type" } signingConfigs { release { storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] } } ... flavorDimensions "build-type" productFlavors { ... } } flutter { source '../..' } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } /Android/app/build.gradle
  • 19.
    name: flutter_example ... environment: sdk: ">=2.17.3<3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: "^1.0.2" device_preview: "^1.1.0" ... dev_dependencies: flutter_test: sdk: flutter flutter_lints: "^2.0.1" ... flutter: uses-material-design: true assets: - assets/icons/ - assets/placeholder/ - assets/images/ ... /pubspec.yaml
  • 20.
    import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine importio.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant class MainActivity: FlutterActivity() { private val openIntent: String = "openIntentChannel" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) MethodChannel(flutterEngine.dartExecutor, openIntent).setMethodCallHandler( IndentHandler(activity) ) } } /Android/app/src/main/kotlin/MainActivity.kt
  • 21.
    import UIKit import Flutter importFirebaseCore @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func setupFirebase() { ... } } /ios/Runner/AppDelegate.swift
  • 22.
    - Flutter에서는 모든것이 위젯입니다. - Stateless Widget & Stateful Widget - Layout Widget ( 링크 ) - Scaffold Widget ( 링크 ) Flutter Widgets
  • 23.
    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( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } /lib/main.dart
  • 24.
    class MyHomePage extendsStatefulWidget { 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(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ 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), )); } } /lib/main.dart
  • 25.
    - Flutter에서 실행할수 없는 코드를 네이티브에서 실행 - Method Channel - Event Channel Platform Channel
  • 26.
    import 'dart:async'; import 'package:flutter/material.dart'; import'package:flutter/services.dart'; class _MyHomePageState extends State<MyHomePage> { static const platform = MethodChannel('samples.flutter.dev/battery'); // Get battery level. String _batteryLevel = 'Unknown battery level.'; Future<void> _getBatteryLevel() async { String batteryLevel; try { final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } ...
  • 27.
    import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity importio.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { private val CHANNEL = "samples.flutter.dev/battery" override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { // This method is invoked on the main thread. call, result -> if (call.method == "getBatteryLevel") { val batteryLevel = getBatteryLevel() if (batteryLevel != -1) { result.success(batteryLevel) } else { result.error("UNAVAILABLE", "Battery level not available.", null) } } else { result.notImplemented() } } } }
  • 28.
    @UIApplicationMain @objc class AppDelegate:FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery", binaryMessenger: controller.binaryMessenger) batteryChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in // This method is invoked on the UI thread. guard call.method == "getBatteryLevel" else { result(FlutterMethodNotImplemented) return } self?.receiveBatteryLevel(result: result) }) GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
  • 29.
    Platform Channel Packages andPlugins (The Boring Flutter Development Show, Ep. 6)
  • 30.
    Tips & BestPractices [Flutter Festival GDG Songdo] GetX, provider, bloc 패턴 비교 분석 - 유병욱
  • 31.
    Tips & BestPractices [Flutter Festival GDG Songdo] Flutter에 Clean Architecture를 얹어보자 - 양수장
  • 32.
    Tips & BestPractices [Flutter Festival GDG Songdo] Flutter 상용화 앱 프로젝트 적용기 - 손민재
  • 33.
    Tips & BestPractices DevFest Songdo 2022 - Flutter Theme 디자이너와 호감작하기
  • 34.
    Tips & BestPractices Github Actions를 활용한 Flutter 배포 자동화하기 - 양수장(GDG Songdo) I 모두콘 2022
  • 35.
    Tips & BestPractices Flutter로 앱, 스토어 정보, 스크린샷 모두를 90개 언어로 자동 번역할 수 있다? - 이준규(GDG Songdo) I 모두콘 2022
  • 36.
    Tips & BestPractices Flutter 앱에서 머신러닝 활용 방법 - 박제창(Flutter Seoul) I 모두콘 2022
  • 37.
    Tips & BestPractices Flutter, 어떻게 해야 더 잘 쓸까? - 유병욱(Flutter Seoul) I 모두콘 2022
  • 38.