2023. 2. 18. 10:22ㆍDevelopers 공간 [Basic]/Frontend
기존에는 안드로이드 앱은 코틀린(Kotlin), 자바(Java) 언어와 안드로이드 SDK*를 이용하여 개발하고, iOS의 경우 스위프트(Swift), Objective-C 언어와 iOS SDK를 이용하여 네이티브 앱을 개발해왔습니다.
이번엔 요즘 크로스 플랫폼 개발 프레임워크 및 SDK로 유행하는 Flutter에 대해서 설명하고자 합니다.
** 크로스 플랫폼(Cross-platform) : Windows, OSX, Linux, iOS, Android 등 다양한 플랫폼에서 사용할 수 있다는 것
Flutter는 Google에서 개발한 크로스 플랫폼 모바일 앱 개발 프레임워크이며, 모바일 앱 개발시 네이티브 앱과 하이브리드 앱이 크로스플랫폼에 대해 일일히 구현해야 하는 어려움을 개선하기 위해 만들어졌습니다.
이런 장점 때문에, 네이티브 앱 개발자들 또한 flutter를 개발하는 모습을 보이고 있으며, 다양한 패키지 및 API를 제공하기 때문에 이점이 많다고 볼 수 있습니다.
- 모바일 웹(Mobile Web) : 웹 브라우저로 실행하는 웹 페이지.
- 웹 앱(Web App) : 웹 브라우저로 실행하며, SPA 형태로 앱과 같이 최적화하여 동작하는 웹페이지
- 하이브리드 앱(Hybrid App) : 앱으로 실행하며, 모바일 웹앱을 패키징 한것입니다.
- Webview기반 App : 웹 위주로 구성, 앱은 꼭 필요한 부분만 사용
- Appview 기반 App : 네이티브위주, 일부 컨텐츠만 웹화면 구성 - 네이티브 앱(Native App) : 앱으로 실행하며, 최적화된 네이티브 언어가 존재합니다.
<구성>
1. 프로젝트 구성 방식
a. Flutter 프로젝트
b. 아키텍처 패턴
c. 프로젝트 구성 예시
2. Dart 기초
a. 기본 문법
b. 위젯이란?
c. 위젯의 종류
3. 기타 특성 정리
a. Class
b. nullable 관련
c. async 관련
d. 변수 접근 방법
e. Provider
글효과 분류1 : 코드
글효과 분류2 : 폴더/파일
글효과 분류3 : 용어설명
글효과 분류4 : 글 내 참조
글효과 분류5 : 글 내 참조2
1. 프로젝트 구성 방식
프로젝트를 구성할 땐, 어떤 방식으로 구현할지 혹은 어떤 정책을 가지고 구현할지에 대한 기초를 가지고 시작하면 좋습니다. 설치부터 시작해서, 어떤식으로 구성하는 것이 좋을지 필자가 구성하는 방식을 소개해보겠습니다.
a. Flutter 프로젝트
- Flutter SDK 설치
- flutter.dev 웹페이지 > "Get Started"에 들어가 Flutter SDK를 다운로드 합니다.
- 원하는 위치에 해당 압축파일를 풉니다. (해당 위치를 기억합니다. 나중에도 유용히 쓰입니다.)
- export PATH="$PATH:해당위치/flutter/bin" 를 ~/.bashrc(Ubuntu) 혹은 ~/.zshenv(Mac) 에 추가합니다.
- 나중에 flutter 명령어가 먹지 않는 경우 source ~/.zshenv 명령어를 실행해줍니다. (Mac)
- flutter doctor : flutter 상태를 진단합니다.
- Flutter : Flutter SDK의 설치 상태
- Android toochain : 안드로이드 개발을 위한 SDK의 설치 상태
- Android license not accepted 문제가 발생하는 경우 flutter doctor --android-licenes라는 명령어를 사용합니다.
- Chrome : Simulate할 Web에 대한 정보
- Xcode : iOS 나 MacOS emulator를 활용하려면 Mac OS 컴퓨터와 적합한 Xcode버전이 필요합니다.
- Android Studio/ IntelliJ IDEA / VS Code : IDE로,Flutter가 Google에서개발되었기때문에, Android Studio가 제일 사용하기 편리합니다.
- Android Studio 설치 : https://developer.android.com/studio 에서 설치 가능합니다.
** Android Studio Plugin : 플러터 프로젝트를 사용하려면 Flutter 플러그인을 설치해야하는 경우가 있습니다. (필자는 Dart와 Flutter Plugin을 활용) - VS Code :https://code.visualstudio.com/ 에서 설치 가능합니다.
- Android Studio 설치 : https://developer.android.com/studio 에서 설치 가능합니다.
- Connected device : Simulate할 연결된 Device 혹은 Emulator에 대한 정보
- USB 케이블을 통해 직접 Device를 연결하거나 AVD(Android Virtual Device) Manager / macOS, iOS Simulator를 활용해 Emulator를 만들어줄 수 있습니다.
- HTTP Host Availability : HTTP host 셋팅이 되었는지 확인합니다.
- 아래와 같이 New Flutter project를 통해서 실행합니다
- Flutter SDK path : 앞서 정한 flutter의 설치 위치를 명시해 줍니다.
- Project name : 진행하는 프로젝트의 이름을 적어줍니다.
- Android language, iOS language : 어떤 언어를 사용할지 선택해 줍니다.
- Platforms : 어떤 플랫폼을 사용할지 선택해줍니다.
다음은 flutter에서 사용하는 명령어 입니다. 자주사용하는 명령어는 빨간색 표시를 하겠습니다.
# 상태 관리
flutter --version
flutter upgrade
flutter doctor
flutter devices
flutter config
flutter config —enable-web # 웹 개발 활성화
flutter config –enable-windows-desktop # for the Windows runner
flutter config –enable-macos-desktop # for the macOS runner
flutter config –enable-linux-desktop # for the Linux runner
# 프로젝트 관리
flutter create myapp
flutter run
flutter run -d chrome
flutter build web # build/web 에 빌드(hosting할때)
flutter build appbundle –target-platform android-arm,android-arm64,android-x64
flutter build apk –split-per-abi –no-sound-null-safety
flutter build apk --release --target-platform=android-arm64
flutter build apk --analyze-size
flutter build apk --split-debug-info
flutter channel stable
flutter upgrade
flutter clean
# 패키지 관리
flutter pub run build_runner watch –delete-conflicting-outputs
flutter pub run build_runner build –delete-conflicting-outputs
# 에뮬레이터
flutter emulators : 종류 보기
flutter emulators –launch Nexus_6API_26 : 실행 하기
- 기본 명령어
- flutter 상태 관리
- flutter --version : dart 및 flutter 버전 확인
- flutter upgrade : flutter 업그레이드
- flutter doctor: flutter 상태 확인
flutter doctor -v : verbose mode로 자세히 보여줍니다. - flutter devices : devices 확인
- flutter config : 개발환경 설정
- flutter channel stable : 현재 flutter 버전을 stable 버전으로 upgrade 하는 것으로, 실행 후에 flutter upgrade 혹은 flutter --version 명령어를 flutter 버전이 변경이 됩니다.
- flutter 프로젝트 관리
- flutter create 이름 : make project
flutter create . project 생성 이후에 프로젝트 repair 및 사라진 파일 복원 - flutter run : 구동하는 방법이며, default로 debug 모드로 실행이됩니다. (hot-reload 기능이 지원됩니다.)
flutter run -d chrome : chrome으로 구동
flutter run --releasae : release 모드로 구동시키는 것입니다. - flutter build : 플러터 빌드하기 (apk, appbundle, ios, linux, macos, windows)
flutter build web
flutter build appbundle –target-platform android-arm,android-arm64,android-x64 : Android 를 play store에 배포 할떄 앱번들 형태로 배포해야 하는데 아래의 명령으로 생성
flutter build apk –split-per-abi –no-sound-null-safety : abi형태로 배포 하면서 null safety 기능 끄기
flutter build apk --release --target-platform=android-arm64
flutter build apk --analyze-size : size analysis 툴
flutter build apk --split-debug-info : dart 2.13이상일 때 Apk등 최종 패키지 사이즈 줄이기, debug정보가 삭제되어 용량이 줄어 든다 - flutter clean : 가끔 빌드가 안될 때, Third-party Library와 build 등을 모두 지우도록
- flutter create 이름 : make project
- flutter 애뮬레이터 관리
- flutter emulators : 종류 보기
- flutter emulators –launch Nexus_6API_26 : 실행 하기
- flutter 상태 관리
// pub 관리
flutter pub version
flutter pub deps
// 패키지 관리
flutter pub get
flutter pub add [패키지 이름]
flutter pub remove [패키지 이름]
flutter pub upgrade
flutter pub downgrade
flutter pub outdated
// 기타
flutter pub global
flutter pub publish
flutter pub run
flutter pub uploader : 패키지 업로더 정보를 수정(추가/제거)합니다.
flutter pub cache : 추가한 의존성에 대한 캐시와 관련된 작업을 합니다.
- 패키지 관리
- Pub : 패키지 의존성 관리 및 프로젝트 정의 등의 역할하는 패키지 매니저
- Dart는 기본적으로 pub 명령어를 그대로 사용합니다.
- flutter는 flutter package 명령어를 사용해 접근합니다.
- pub 관리
- flutter pub version : 버전 확인
- flutter pub deps : 의존성 출력 (트리 형태로 출력한다.) 의존성 그래프를 트리구조로 보여줍니다.
- 패키지 관리
- flutter pub get : 플러그인들을 사용할 수 있게 프로젝트로 가져온다. pubspec.yaml 에 있는 내용을 다운로드합니다.
- flutter pub add [패키지 이름] : 의존성 추가 (자동으로 yaml 파일이 수정된다.)
- flutter pub remove [패키지 이름] : 의존성 제거 (자동으로 yaml 파일이 수정된다.)
- flutter pub upgrade : 업그레이드가 가능한 플러그인을 업그레이드 한다. 패키지의 버전을 최신 버전으로 올립니다.
- ex) flutter pub upgrade —major-versions : 최신버전업그레이드
- flutter pub downgrade : 특정 패키지의 버전을 낮춥니다.
- flutter pub outdated : 업그레이드가 필요한 플러그인을 찾아준다. (plug-in들이 null Safety지원 된는 걸로 Update하기 위해 아래 명령 실행)
- 기타
- flutter pub global : 패키지를 전역으로 사용할 수 있도록 변경합니다.
- flutter pub publish : 패키지를 배포합니다.
- flutter pub run : 스크립트를 실행합니다.
- flutter pub uploader : 패키지 업로더 정보를 수정(추가/제거)합니다.
- flutter pub cache : 추가한 의존성에 대한 캐시와 관련된 작업을 합니다.
- Pub : 패키지 의존성 관리 및 프로젝트 정의 등의 역할하는 패키지 매니저
-----------------------------------------------------------------------------------------------------
<패키지 매니저 예시>
- homebrew : MacOS에서 패키지를 설치하고 관리하는 맥용 패키지 관리자
- Mac을 사용하면 flutter 셋팅 시에 사용할 수 있습니다.
- ex) $ brew install node
- ex) $ brew install cocoapods
- gem : 루비에서 사용하는 라이브러리 패키지 시스템
- 보통 brew대신 gem으로 패키지를 관리하시는 분들도 있으십니다.
- ex) $ sudo gem install cocoapods
- CocoaPods : iOS 개발에 사용되는 의존성 관리자(for Swift and Objective-C)
- iOS프로젝트를 관리할 때 사용합니다.
- ex) ios/Pods 폴더와 ios/Podfile.lock을 지우고 ios폴더에서 pod install 명령어를 실행하면 다시 프로젝트를 셋팅할 수 있습니다.
- CocoaPods는 Ruby로 제작되어있습니다. (기본 Ruby는 OS X(맥북)에서 사용할 수 있다)
- ex) $ pod --version
- ex) $ pod install --repo-update
- iOS프로젝트를 관리할 때 사용합니다.
- NPM(Nodejs Package Manager) : Nodejs의 대표 패키지 매니저
- flutter 사용시에는 주로 사용하지 않지만, 다른 frontend 프로젝트나, backend 구축시에 주로 사용합니다.
- ex) $ npm ini : 패키지 시작
- ex) $ npm install 패키지명, npm i 패키지명 : 패키지 설치
- ex) $ npm install 패키지명, npm install 패키지명 --dev : 프로젝트 내에서 사용할 패키지
- ex) $ npm install 패키지명 --save-dev : 프로젝트 내에서 사용하지는 않지만, 프로젝트를 실행하기 위한 목적등으로 패키지를 설치
- ex) $ npm install 패키지명 --global : 모든 패키지들이 공통으로 사용할 패키지들을 설치
- ex) $ npm remove 패키지명, npm r 패키지명 : 패키지 삭제
-----------------------------------------------------------------------------------------------------
- 프로젝트 기본 구조
- .dart_tool :
- .idea :
- android
- app
├──src
├──debug
└── AndroidManifest.xml
├──main
├──java
├──kotlin
├──res
└──AndroidManifest.xml : 카메라&마이크 등 권한 추가 "<uses-permission>",
프로젝트 Bundle ID 위치 "<manifest>",
URL 스킴 설정 및 API 키 추가 "<activity>"
(3가지 AndroidManifest.xml중에 가장 중요합니다)
**manifest : 안드로이드 시스템이 앱의 코드를 실행하기 전에 확보해야 하는 앱에 대한 필수 정보를 시스템에 제공하는 목록
├──profile
└──AndroidManifest.xml
└──build.gradle : Android 최소 SDK 버전을 명시할 수 있다.(compileSdkVersion) - gradle
└──wrapper
├──gradle-wrapper.jar
└──gradle-wrapper.properties - .gitignore
- build.gradle : 빌드에 사용할 SDK 버전, JAV 버전, application version, 사용하는 library등 모듈의 빌드 방법이 정의된 build script입니다.
** gradle : build tool로 ant, maven, gradle 등의 빌드 도구 들이 있지만 gradle를 사용하고 있습니다. - gradle.properties : 안드로이드에서 어떤 기능들을 활용한 것인지 명시해놓는 것, androidX를 활용할지도 체크할 수 있습니다.(android.useAndroidX=true)
** JetPack : 기존의 "Support Library"라는 라이브러리 모음집을, 개선한 모음집
** androidX : JetPack 내의 라이브러리를 모은 패키지
** Jetifier : 이전에 android에서 사용된 "support library"로 만들어진 third party library들을 AndroidX 프로젝트와 호환되도록 변환해주는 툴 - gradlew
- gradlew.bat
- local.properties
- projectOOO_android.iml
- settings.gradle : 중요
- app
- ios
- Podfile: IOS version명시(오른쪽예시), 카메라&마이크 등 권한 추가
- Flutter
├──AppFrameworkInfo.plist
├──Debug.xcconfig
├──flutter_export_environment.sh
├──Generated.xcconfig
└──Release.xcconfig - Runner
├──Assets.xcassets
├──Base.IProj
├──AppDelegate.swift : 패키지 사용시 API키 추가 등을 하는데 사용합니다.
├──GeneratedPluginRegistrant.h
├──GeneratedPluginRegistrant.m
├──Info.plist : 카메라&마이크등권한추가 할때 description을 적어주는 부분
└──Runner-Bridging-Header.h - Runner.xcodeproj
├──project.xcworkspace : 프로젝트를 위한 Xcode 환경 설정위치
├──xcshareddata
└──project.pbxproj : 프로젝트 Bundle ID 위치 - Runner.xcworkspace
├──xcshareddata
└──contents.xcworkspacedata - .gitignore
- lib : 실제 구현 하는 부분
- test :
- web
- icons :
- favicon.png :
- index.html : 패키지 사용시 API키추가 등을 하는데 사용합니다.
- manifest.json :
- .gitignore
- .metadata
- analysis_options.yaml
- projectOOO.iml
- pubspec.lock
- pubspec.yaml
- README.md
- 코드의 시작 main.dart
- void main() : 초기셋팅을 하기 위한 함수입니다. 예를 들어 아래와 같은 일들을 합니다.
- WidgetsFlutterBinding.ensureInitialized() : Flutter Engine과의 상호 작용을 위해 async하게 동작하도록, 위젯 바인딩을 보장하기 위한 명령어입니다. async한 작업이 필요하면 꼭 초기화해주어야합니다.
** Flutter engine : Flutter는 Dart로 구성되어 있는 코드들을 Flutter Engine을 통해서 플랫폼(Android, iOS 등)과 통신하게 되어있고, 이 과정이 JIT컴파일이던 AOT컴파일이던 상관 없이 동작합니다. - GesetureBinding.instance.resamplingEnabled = true : touch device 중 frequency가 낮은 것들을 위한 pointer event resampling을 가능하게 해주는 명령어 입니다.
- import gesture.dart
- SystemChrome.setPreferedOrientations : 스마트 디바이스가 가로로 회전시키더라도 화면이 로테이션되지 않도록 고정하는 방법입니다.
- import services.dart
- ex) DeviceOrientation.portraitUp : 세로모드
- ex) DeviceOrientation.portraitDown : 위아래 모드
- ex) DeviceOrientation.landscapeRight
- KakaoConext.clientId : oauth를 위한 카카오로 로그인 가능하도록 하는 것
- import kakao_flutter_sdk/auth.dart
- KakaoContext.clientId = '카카오 네이티브 앱 키';
- setupLocator() : 뒤에 설명할 stacked 패키지의 Locator를 셋팅하는 명령어 입니다.
- WidgetsFlutterBinding.ensureInitialized() : Flutter Engine과의 상호 작용을 위해 async하게 동작하도록, 위젯 바인딩을 보장하기 위한 명령어입니다. async한 작업이 필요하면 꼭 초기화해주어야합니다.
- StatefulWidget, StatelessWidget : flutter를 구현하기 위한 기본 위젯이며, stful, stless 명령어를 통해 초기화할 수 있습니다.
- void main() : 초기셋팅을 하기 위한 함수입니다. 예를 들어 아래와 같은 일들을 합니다.
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(
...
);
}
}
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(
...
);
}
}
b. 아키텍처 패턴
- 아키텍처 패턴과 디자인 패턴
- 아키텍처(Architecture) 패턴 : Software Architecture 상 구조나 고수준의 문제점에 대한 솔루션
ex) Layered, Client-server, Master-slave, Pipe-filter, Broker, Peer-to-peer, Event-bus, MVC, MVVM, Blackboard pattern, Interpreter pattern - 소프트웨어 디자인(Design) 패턴 : 각각의 Module 상 어떤 것을 하는지, 클래스의 범위, 함수의 목적 등의 코드 수준의 디자인 솔루션
ex) 생성 패턴 : 싱글톤 (Singleton), 팩토리 메소드 (Factory Method), 추상 팩토리 (Abstract Factory), 빌더 (Builder), 프로토타입 (Prototype)
ex) 구조 패턴 : 어댑터 (Adapter), 브릿지 (Bridge), 컴포짓 (Composite), 데코레이터 (Decorator), 퍼사드 (Facade), 플라이웨이트 (Flyweight), 프록시 (Proxy)
ex) 행동 패턴 : 책임 연쇄 (Chain-of-Responsibility), 커맨드 (Command), 인터프리터 (Interpreter), 이터레이터 (Iterator), 중재자 (Mediator), 메멘토 (Memento), 옵저버 (Observer), 상태 (State), 전략 (Strategy), 템플릿 메소드 (Template Method), 비지터 (Visitor)
- 아키텍처(Architecture) 패턴 : Software Architecture 상 구조나 고수준의 문제점에 대한 솔루션
- 아키텍처 패턴 종류
- MVC : Model-View-Controller
- 1. 사용자의 Action들은 Controller에 들어오게 됩니다.
- 2. Controller는 사용자의 Action를 확인하고, Model을 업데이트합니다.
- 3. Controller는 Model을 나타내줄 View를 선택합니다.
- 4. View는 Model을 이용하여 화면을 나타냅니다.
- 특징 : (+) 가장 단순, (-) View와 Model 사이의 의존성이 높다. 유지보수가 어렵다
- MVP : Model-View-Presenter
- 1.사용자의 Action들은 View를 통해 들어오게 됩니다.
- 2.View는 데이터를 Presenter에 요청합니다.
- 3.Presenter는 Model에게 데이터를 요청합니다.
- 4.Model은 Presenter에서 요청받은 데이터를 응답합니다.
- 5.Presenter는 View에게 데이터를 응답합니다.
- 6.View는 Presenter가 응답한 데이터를 이용하여 화면을 나타냅니다.
- 특징 : (+) View와 Model의 의존성이 없다, (-) View와 Presenter 사이의 의존성이 높다
- MVVM : Model-View-ViewModel
- 1. 사용자의 Action들은 View를 통해 들어오게 됩니다.
- 2. View에 Action이 들어오면, Command 패턴으로 View Model에 Action을 전달합니다.
- 3. View Model은 Model에게 데이터를 요청합니다.
- 4. Model은 View Model에게 요청받은 데이터를 응답합니다.
- 5. View Model은 응답 받은 데이터를 가공하여 저장합니다.
- 6. View는 View Model과 Data Binding하여 화면을 나타냅니다.
- 특징 : (+) View와 Model 사이의 의존성이 없습니다
- MVC : Model-View-Controller
-----------------------------------------------------------------------------------------------------
Model : 어플리케이션에서 사용되는 데이터와 그 데이터를 처리하는 부분입니다.
View : 사용자에서 보여지는 UI 부분입니다.
Controller : 사용자의 입력(Action)을 받고 처리하는 부분입니다.
Presenter : View에서 요청한 정보로 Model을 가공하여 View에 전달해 주는 부분입니다. View와 Model을 붙여주는 접착제..? 역할을 합니다
View Model : View를 표현하기 위해 만든 View를 위한 Model입니다. View를 나타내 주기 위한 Model이자 View를 나타내기 위한 데이터 처리를 하는 부분입니다.
-----------------------------------------------------------------------------------------------------
- MVVM 패턴 구현 방법 : Stacked Package, Bloc Package 등
- MVVM 스타일 아키텍처 사용위해 provider기능을 제공하는 패키지는 stacked, block과 같은 패키지들을 사용 가능하며, 필자는 Stacked 패키지를 주로 활용하기 때문에 Stacked 패키지로 설명을 하겠습니다.
** provider : 자세하게는 하기에 설명하겠습니다. MVVM 패턴을 사용하기에 적합한 라이브러리라고 생각하면 되며, viewmodel은 ChangeNotifier를 통해, view는 listener를 통해 구현됩니다. - 셋팅 방법
- Stacked Package설치하기
- https://stacked.filledstacks.com/ 로 이동해 위에서 프로젝트를 생성하는 방식이 아닌 위의 매뉴얼을 따라 설치합니다.
dart pub global activate stacked_cli
stacked create app 프로젝트 이름 - stacked 패키지는 v3.1.0 이전에는 직접 프로젝트를 만드는 것이 아닌, 기존의 프로젝트에 lib/app/app.dart 파일에 내용을 적어 셋팅을 하는 방법이었습니다. 해당 버전의 패키지를 활용하시려면 다른 정보를 활용하시기 바랍니다.
- 위의 방법으로 설치하면, app폴더와, 기본적인 ui폴더, 그리고 stacked, stacked_services, build_runner, stacked_generator, mockito 등 필요한 dependency들이 모두 버전이 맞춰 설치되어있습니다.
- 위의 방법으로 설치하면, stacked를 구동하기 위해 main.dart의 main() 함수 내의 setupLocator()함수가 호출되어있으며, MaterialApp() 내의 onGenerateRoute, navigatorKey가 stacked패키지 함수로 잘 지정되어있습니다.
- https://stacked.filledstacks.com/ 로 이동해 위에서 프로젝트를 생성하는 방식이 아닌 위의 매뉴얼을 따라 설치합니다.
- app/app.dart 파일 내용에서 종속성을 만들어 줍니다.
(명령어로 진행합니다. 하기에 더보기를 누르시면 자세히 알 수 있습니다.)
...
@StackedApp(
routes:[
...
MaterialRoute(page: P2_0_Page1View),
CupertinoRoute(page: P2_0_Page2View, fullscreenDialog: true),
],
dependencies :[
LazySingleton(classType: AuthService),
Singleton(classType: UserService),
...
Factory( classType: Counter),
Presolve(classType: SharedPreferencesService, presolveUsing: SharedPreferencesService.getInstance),
]
) - app 폴더의 업데이트를 위해 아래 명령어를 진행합니다.
flutter pub run build_runner build --delete-conflicting-outputs
stacked generate - 이후에 아래 "더보기"와 같이 view와 view model 형식으로 구현을 이어갑니다.
간단히 소개하고 자세한 내용은 stacked에 관한 글을 따로 만들도록 하겠습니다.
https://stacked.filledstacks.com/ 페이지에 자세히 나와 있으니 참고 부탁드립니다.
- Stacked Package설치하기
- MVVM 스타일 아키텍처 사용위해 provider기능을 제공하는 패키지는 stacked, block과 같은 패키지들을 사용 가능하며, 필자는 Stacked 패키지를 주로 활용하기 때문에 Stacked 패키지로 설명을 하겠습니다.
-----------------------------------------------------------------------------------------------------
<Stacked 프로젝트의 main.dart>
import 'package:flutter/material.dart';
import 'package:projectcam_v2/app/app.bottomsheets.dart';
import 'package:projectcam_v2/app/app.dialogs.dart';
import 'package:projectcam_v2/app/app.locator.dart';
import 'package:projectcam_v2/app/app.router.dart';
import 'package:projectcam_v2/ui/common/app_colors.dart';
import 'package:stacked_services/stacked_services.dart';
void main() {
setupLocator();
setupDialogUi();
setupBottomSheetUi();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: Theme.of(context).copyWith(
primaryColor: kcBackgroundColor,
focusColor: kcPrimaryColor,
textTheme: Theme.of(context).textTheme.apply(
bodyColor: Colors.black,
),
),
initialRoute: Routes.startupView,
onGenerateRoute: StackedRouter().onGenerateRoute,
navigatorKey: StackedService.navigatorKey,
navigatorObservers: [
StackedService.routeObserver,
],
);
}
}
<Stacked 프로젝트에서 View 구현>
lib/ui/views/login/login_view.dart와 lib/ui/views/login/login_viewmodel.dart를 만들고 싶다면 아래 명령어를 실행합니다.
viewmodel.dart는 BaseViewModel, FutureViewModel 등을 활용해 구현할 수 있으며, 위 lib/ui/view 등의 초기 기본 설정은 stacked.json에서 확인할 수 있습니다.
stacked create view login
stacked delete view login
<Stacked 프로젝트의 추가기능>
- DialogService(알림)와 BottomSheetService(알림), Navigation Service(페이지 이동)를 기본으로 제공합니다
- stacked_annotations 패키지를 활용해 FromView(입력 기능) 기능을 제공합니다.
stacked create service A
stacked create bottom_sheet B
stacked create dialog C
<Stacked app.dart에 추가할 때 디자인(Design) 패턴 이해>
- Singleton :클래스에서 인스턴트를 오직 하나만 만드는패턴. 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고, 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴합니다. Service Layer는 하나의 객체로만 돌려쓰는게 메모리관리에 효율적이라서 싱글톤패턴을 주로 활용합니다.
- LazySingleton : 처음에 그 객체가 선언될때 인스턴스를 생성
- Factory : Singleton 패턴과 비슷하지만, 생성하는 방법이 다릅니다. Object의 복잡한 생성 과정을 클라이언트가 볼 필요없이 클라이언트는 Factory에 생성할 객체에 대한 주문하며, Factory 클래스가 내부 각각 instance를 생성하는 책임을 집니다.
-----------------------------------------------------------------------------------------------------
c. 프로젝트 구성 예시
- lib : o/p 등 붙는 이유는 관련성있거나, view-viewmodel 간의 여러개의 파일을 묶기 위해서 합니다.
├── main.dart
├── app (자동생성 및 수정) : stacked 패키지 router와 locator 등
├── ui (자동생성) : 실제 상태관리가 되는 페이지들 (stacked 참조)
├── common/OOO.dart : ui를 구성하기 위한 셋팅 값
├── bottom_sheets/ABC/ABC_sheet.dart & ABC_sheet_model.dart : bottom_sheet 페이지 서비스
├── dialogs/ABC/ABC_dialog.dart & ABC_dialog_model.dart : dialog 알람 서비스
└── views/p1_0_OOO_view.dart & p1_0_OOO_viewmodel.dart :view와 view-model
├── services (자동생성) : 미리 만들어 놓고 재사용 하는 서비스 (stacked 참조)
├── basic_native_service.dart:GPS, storage등 native app의 서비스
├── basic_profile_service.dart:firebase 등 Analytics와 관련된 서비스
├── basic_server_service.dart:HTTP API와 관련된 서비스
├── o2_0_OOO_service.dart:현재 App의 상태관리와 관련된 provider 서비스
├── server_service.dart:custom 서버와의 interface 서비스
└── auth_service.dart:oauth와 회원 가입과 관련된 서비스
├── OBJECTS/o2_0_OOO.dart : 미리 정의된 오브젝트들
├── PAGES : 실제 페이지들로, 사용되는 페이지는 view와 view-model로 이루어져 있습니다.(stacked 참조)
├── LAYOUT/OOO_view.dart : 하기 페이지들의 정적인 레이아웃을 미리 정해놓는 것
└── STATICS/OOO_view.dart : empty_page/policy_page/error_page 나 Layered 되는 정적인 페이지를 구현
└── SHARED : 공유되는 것들을 담는 곳 - 프로젝트 구성 예시 : tree 구조로 아래와 같이 구성한 뒤 시작합니다.
2. Dart 기초
Dart는 flutter를 활용한 개발에 사용하는 언어 입니다. 아래의 기본적인 특징에 대해 파악한뒤 시작하는 것이 좋습니다.
a. 기본 문법
- 자료형
- enum
ex) enum Version {fit,needToBeUpdated,fail} - num : int+double
- var : 타입 대충(int, double, bool, String), 처음 선언한 type 유지
- dynamic : 타입 대충, 처음 선언한 type을 유지하지 않습니다.
- final : 한 번 설정한 값을 변경할 수 없다. 다른 값으로 변경하려고 시도하면 컴파일 오류가 발생한다. final은 런타임에서 결정되는 값도 설정할 수 있다.
- const : 한 번 설정한 값을 변경할 수 없다. 다른 값으로 변경하려고 시도하면 컴파일 오류가 발생한다. const의 경우, 컴파일 타임에서 상수를 정의할 수 있다.
- _변수명 : class 내 private 변수로 할 수 있다.
- 변수명? : 개체가 NULL일 수 있음(선언시)
ex) int? temp;
** null : 값이 없음, none : 값이 아님, nan : 숫자 아님 - 변수명! : 개체가 NULL일수 없음(사용시)
ex) int temp = temp2!;
- enum
- Collection
- List : 데이터의 순서가 있고 중복을 허용함(배열, Array) []
- 생성
var vegetables = List();
var fruits = ['apples', 'oranges'];
var ABC = List<int>.filled(3,0,growable:true) - 추가
fruits.add('kiwis');
fruits.addAll(['grapes', 'bananas']); - 정보
assert(fruits.length == 5);
assert(fruits.indexOf('apples') == 0); - 얻기
var appleIndex = fruits.indexOf('apples'); - 제거
fruits.removeAt(appleIndex);
fruits.clear();
- 생성
- Set : 데이터의 순서가 없고 중복을 허용하지 않음 []
- 생성
var ingredients = Set(); - 추가
ingredients.addAll(['gold', 'titanium', 'xenon']);
ingredients.add('gold'); - 정보
assert(ingredients.length == 3);
assert(ingredients.contains('titanium'));
assert(ingredients.containsAll(['titanium', 'xenon'])); - 얻기 : X
- 제거
ingredients.remove('gold'); - 집합
var nobleGases = Set.from(['xenon', 'argon']);
var intersection = ingredients.intersection(nobleGases)
- 생성
- Map : 키(Key)와 값(Value)로 구성된 클래스로서 키는 중복을 허용하지 않고 값은 중복을 허용함 {}
- 생성
var hawaiianBeaches = { 'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'], 'Big Island': ['Wailea Bay', 'Pololu Beach']};
var searchTerms = Map();
var nobleGases = Map<int, String>();
var nobleGases = {54: 'xenon'}; - 얻기
assert(nobleGases[54] == 'xenon');
var keys = hawaiianBeaches.keys; - 정보
assert(values.length == 3);
assert(nobleGases.containsKey(54));
assert(Set.from(keys).contains('Oahu'));
assert(values.any((v) => v.contains('Waikiki'))); - 제거
nobleGases.remove(54);
- 생성
- List : 데이터의 순서가 있고 중복을 허용함(배열, Array) []
- operator
- 일반연산자 : +, -, *, /, ~/ (몫, int), % (나머지, int)
- 증감연산자(후위,전위 가능) : ++,--
- 비교 연산자 : ==, !=, >, <, >=, <=
- 논리연산자 : !=(다르다), ==(같다), &&(and), ||(or), !(부정)
- 타입검사자 : is, is! (같은 타입인지)
ex) a is int, text is! int - ?? : "if null operator"라고 하며, null 인지 확인하는 것
ex) String a = b ?? 'hello'; - ??= : null-aware assignment라고 합니다.
- result = (a > b) ? x : y; : 삼항연산자(Tenary Operator)
- .. (double dots) : cascade notation이라고 하며, 같은 object에 대해서 operation을 사용하고 싶을 때 사용
ex) 기존 var paint = Paint(); --------- paint.color = Colors.black;
ex) 새로 var paint = Paint() --------- ..color = Colors.black - … (triple dots) : Spread Operator이라고 부르며 여러개의 value들을 다른 list와 함께 사용할때 사용
ex) var list = [1, 2, 3]; ------------- var list2 = [0, ...list]; ------------- 결과는 [0,1,2,3]
- 함수
- (input){function}; : anonymous function(무명 함수)
- (input) => function; : lambda function
- void function(){} : 기본 함수
- Required Parameter : 아무것도 하지 않고 들어간 input 혹은 뒤에 나올 Optional Parameter 앞에 required가 붙은 경우입니다.
- Optional Parameter
- Positional Paramter : “[]” 사용해 넣는 방법으로, 위치가 중요한 파라미터. 순서대로 넣어주어야 합니다.
- Named Parameter : “{}” 를 사용해 넣는 방법으로, 선택적으로 사용가능함을 의미합니다. 다만, required하면 무조건 필수로 입력해야 합니다. (required와 {}는 독립)
- Default Parameter : Optional Parameter는 "=" 로 초기화 값을 정해줄 수 있습니다
- loop 순환문
- for(int i=0; i<6; i++){}
- for(string s in rainbow){}
- rainbow.foreach(함수)
- 함수예시 : (element){print(element);}
- while(mylist.length!=6){}
// Anonymous Function
(number) {return number%2==0;}
// Lambda Function
(number) => number%2==0;
// Loop - for
List<string> rainbow = [‘빨’, ‘주’, ‘노’, ‘초’]
for(string s in rainbow){}
// Loop - while
Set<int> mylist = {3,4,5};
while(mylist.length!=6){}
b. 위젯이란?
- Widget 이란?
- 일반적 뜻 : 독립적으로 실행되는 작은 프로그램으로, 주로 그래픽이나 데이터 요소를 처리하는 함수를 가지고 있습니다.
- Flutter 내 뜻: APP의 UI를 만들고 구성하는 모든 기본 단위 요소 (Image, TextField 등)
- 눈에 보이지 않는 요소들까지 위젯(center, column, padding, row 등)
- 결론적으로 Flutter에서는 모든 것이 Widget이다!
- 위젯(Widget) 종류
- PreferredSizeWidget - 제한이 없을때 미리 설정한 기본크기를 가지게 하는 위젯
ex) AppBar, PreferredSize, TabBar - ProxyWidget : 새 위젯을 만들지 않고 자식 위젯을 제공받는 위젯
- InheritedWidget : 상태를 후손 위젯들이 read할 수 있게 하는 위젯
ex) ButtonTheme, CupertinoTheme, IconTheme, ListTileTheme, MediaQuery - ParentDataWidget : 자식이 부모 위젯의 레이아웃을 설정할 수 있게끔 해주는 위젯
ex) Flexible(+Expanded), Positioned
- InheritedWidget : 상태를 후손 위젯들이 read할 수 있게 하는 위젯
- RenderObjectWidget : RenderObjectElement들을 위한 설정을 제공하는 위젯. RenderObjectElement는 실제로 렌더링을 제공하는 RenderObject를 감싼 클래스
- LayoutBuilder
- LeafRenderObjectWidget : Texture, RichText
- ListWheelViewport
- MultiChildRenderObjectWidget : Flex(+Column, Row), Flow, Stack, Viewport
- RenderObjectToWidgetAdapter
- SingleChildRenderObjectWidget : Align(+Center), AspectRatio, Baseline, ClipRRect, ConstrainedBox, FadeTransition, FittedBox, LimitedBox, Opacity, Padding, RotatedBox, SizedBox, SliverPadding, Transform, UnconstrainedBox
- SliverWithKeepAliveWidget
- Table
- Stateless Widget : 상태가 없는 정적인 위젯, 이전 상호작용의 어떠한 값도 저장하지 않으며 로드될 때 한 번만 그려지는 State가 없는 위젯
- 스크린상에 존재만 할 뿐 아무것도 하지 않으며, 어떠한 실시간 데이터도 저장하지 않음
- 어떤 변화(모양, 상태)를 유발시키는 value값을 가지지 않음
- ex) AlertDialog, AnimatedIcon, Card, Chip, Container, Dialog, Divider, Drawer, FloatingActionButton, GridTile, Icon, IconButton, ImageIcon, ListTile, Placeholder, SafeArea, ScrollView, SnackBar, Spacer, Tab, Text, Theme, Title, VerticalDivider, Visibility
- Stateful Widget : 계속 모양 움직이거나 상태 변화가 있는 위젯, Value 값을 지속적으로 추적 보존하며 위젯이 동작하는 동안 Data 변경이 필요한 경우 화면을 다시 그려서 변경된 부분을 위젯에 반영하는 동적인 위젯
- 사용자의 interaction에 따라서 모양이 바뀜
- 데이터를 받게 되었을 때 모양이 바뀜 (예, TextField에서 입력되면서 모양과 상태가 바뀔 때)
- ex) AppBar, Image, MaterialApp, NestedScrollView, Overlay, PageView, ProgressIndicator, RefreshIndicator, Scaffold, SliverAppBar, TabBar, TabBarView, TextField
- PreferredSizeWidget - 제한이 없을때 미리 설정한 기본크기를 가지게 하는 위젯
c. 위젯의 종류
위젯의 종류에 대해 분류해 나열해보려고합니다.
파란색은 stacked 패키지와 관련된 것을의미하며, 필자가 주로 사용했던 위젯들은 빨간색으로 표시하겠습니다.
- 기본 Widget
- MaterialApp : node tree의 top에서 담는 그릇
- title : 프로젝트 이름을 명시합니다.
- theme : application 전체적인 테마를 결정합니다.
- initialRoute/home : 초기 화면을 선택합니다.
- onGenerateRoute : stacked 서비스 사용시 사용
- navigatorKey :stacked 서비스 사용시 사용
- navigatorObservers : stacked 서비스 사용시 사용
- localizationsDelegates: 기본적으로 플러터는 US English 지역화만 제공합니다. 다른 언어에 대한 지원을 추가하려면, flutter_localizations 패키지를 추가하고 MaterialApp에 관련 속성들을 정의해야합니다.
- supportedLocales: 언어 코드 외에 더 많은 정보를 제공해야 적절하게 지역화될 수 있습니다.
- Scaffold : 발판
- 최상단Appbar,
- 중간Body,
- 최하단BottomNavigationBar,
- 버튼FloatingActionButtonLocation,
- 왼쪽drawer
- MaterialApp : node tree의 top에서 담는 그릇
- 화면 배치 Widget
- Container : 하나의 child를 가지는 layout
- Column : 수직 방향으로 위젯들을 배치하는 layout
- Row: 수평 방향으로 위젯들을 배치하는 layout
- Stack : 여러 위젯을 순서대로 쌓이게 한다. 사용 방법은 row, column과 같으며 먼저 쓴 위젯이 제일 아래에 깔리게 된다.
- Flex : Row,Column을 전부 할 수 있는 class
- Card : 아이템 등에 사용하는 카드형태의 widet (ListView, GridView에 사용)
- Layout 및 사이즈관련 Widget
- SizedBox : child widget의 size를 강제하기 위해서 혹은 Row, Column에서 widget 사이에 빈 공간을 넣기 위해서
- Padding : right,left,top,bottom을 EdgeInsets 오브젝트로 padding을 주기 위해
- clipRRect : 이미지 모서리 둥글게 하기 가능
- Spacer : 빈공간을 만드는 위젯 (flex속성 가능)
- 위치 정렬 Widget
- Center : Center Widget은 child Widget을 Parrent Widget 정 중앙에 위치시키기 위해 사용,
- 하지만 double widthFactor, double heightFactor없이 쓰면 걍 깡통
- Align : Align으로 감싸고 alignment속성에서 위치를 설정
- Center : Center Widget은 child Widget을 Parrent Widget 정 중앙에 위치시키기 위해 사용,
- Cover Widget(보호)
- SafeArea : 기기별로 화면의 padding을 만들어주는 것.
- 보통 SafeArea>Scaffold (Scaffold의 body를 이 위젯으로 감싸주면 깔끔하게 처리가 가능)
- InkWell : Container와 같이 제스쳐기능을 제공하지 않는 위젯을 래핑하여 onTap 기능 제공, 물물결모양 애니메이션 발생
- FittedBox : 내용물 양에 따라 크기가 확장되는 위젯에 의해 overflow가 발생하는 경우 device 사이즈를 넘지 않도록 해주는 것
- Positioned : Stack에서 자식의 위치를 설정합니다.
- Expanded : Expanded 는 보통 Column, Row Widget 과 많이 쓰인다.
- Column 과 Row Widget 은 children 에 할당되는 Widget 들의 크기에 따라 자동으로 공간을 할당한다.(크기가 지정되지 않은경우 동일한 크기로 자동 할당)
- 이때 각 Widget의 부모에 Expanded Widget을 사용하고 "flex:" 에 값을 할당하면 할당된 flex 에 따라 비율을 조정한다.(위의 예제에서는 flex값에 상관없이 무조건 전체 크기를 할당받는다.
- Flexible : Expanded의 조상 클래스, 고정된 크기보다 더 커질 수 없도록 크기에 제한
- SafeArea : 기기별로 화면의 padding을 만들어주는 것.
- 위젯 내 사용을 위한 위젯
- AppBar : scaffold 등의 AppBar
- TabBar
- Tab
- TabBarView
- BottomNavigatorBar
- List Viewer
- ListView() : 자식들을 수직 또는 수평 방향으로 배치할 수 있고 스크롤 Display할 때
- itemCount
- itemBuilder : ListTile()
- GridView(): 갤러리같은것 2차원 배열로 Display할 때
- itemCount
- shrinkWrap:
- gridDelegate
- itemBuilder : ()=>GestureDetector가능
- PageView() : 스크롤해서 페이지를 넘길 수 있는 위젯
- ListTile()
- SingleChildScrollView()
- ListView() : 자식들을 수직 또는 수평 방향으로 배치할 수 있고 스크롤 Display할 때
- Object
- 글자
Text(‘내용’)
TextStyle(fontsize:--, height:--)
FontWeight.bold : 글자 두께 - 모양
Icon(Icons.home)
IconButton() : “onPressed:” 를사용할 때 - 앱
ThemeData(appBarTheme:, dialogTheme:,scaffoldBackground:, accentColor…) :등등 미리 지정
AppBarTheme() : appBarTheme에 사용
ThemeData() : dialogTheme에 사용
TextTheme(subtitle1:, bodyText1: …) : 등등 미리 지정, textTheme에 사용
Image() : ex) Image.network('/RsqCz') 혹은 Image.asset("../image.png") - 추상
Color(Colors.white)
EdgeInsets : 주변 edge들을 표현시 ex) EdgeInsets.zero, EdgeInsets.all(8.0), EdgeInsets.only(left:40.0) - 기타
Curves.fastOutSlowIn : 애니메이션의 커브를 설정
- 글자
3. 기타 특성 정리
위에 설명한 기본적인 것들 이외에 알고 있으면 좋을 만한 키워드들을 나열해보았습니다.
a. Class
- Constructor/Getter/Setter 재정리
- Constructor : 생성자가 없는 경우 Default Contructor(기본 생성자)로 제공됩니다.
- 1. Contructor(이름 없는 생성자) : Default Contructor가 제공되기에 안만들어주어도 된다. 다만, 선언해준다면 단 한개만 가질 수 있습니다.
- 1-1. Default Constructor(생성자) : 클래스명(){}으로 자동으로 만들어진 생성자입니다.
- 1-2. Constructor(생성자) : 클래스명(변수){변수초기화} 혹은 클래스명(변수)로 만들어진 생성자로, Default Contructor 대신 사용됩니다.
- 1-3. Initializer Lists(초기화 리스트) : 클래스명(): 초기화리스트{} 형태로 만들어진 생성자이며, 내부 구현된 함수보다 가장먼저 변수를 초기화합니다.
- 2. Named Contructor(이름 있는 생성자) : 클래스명.함수명(){} 형태로 만들어진 생성자로 여러가지 만들수 있으며, 만들고 싶다면 기본생성자를 꼭 넣어주어야한다.
- 1. Contructor(이름 없는 생성자) : Default Contructor가 제공되기에 안만들어주어도 된다. 다만, 선언해준다면 단 한개만 가질 수 있습니다.
- Getter : (함수명=>return값)형태로, private을 접근이 가능하도록 getter만들어줍니다.
- int get C => _C;
- Setter : (set 함수명(인풋)=>할당) 형태로, private을 변경이 가능하도록 setter만들어줍니다.
- set C(num value) => _C=value
- Relation
- implements : abstract 깡통 class를 상속하기 위해
ex) abstract class ABC{}를 상속한 후에 override 하기위한 method에 @override를 붙여준다. - extends : 상속
override하기 위해서 @override를 붙여준다 - with : mixin 기능
추가로 할때, 상속하지 않고 class 기능을 가져오거나 overrid할 수 있다.
ex) class DarkGoblin extends Goblin with Hero : Goblin class와 Hero class 기능 모두 가질 수 있다. - on
- implements : abstract 깡통 class를 상속하기 위해
- Constructor : 생성자가 없는 경우 Default Contructor(기본 생성자)로 제공됩니다.
class Point{
Point(double x_in, double y_in) {};
Point({this.x, this.y});
Point(this.x, this.y);
//NamedConstructor
Point.initialize(double x_in, double y_in):x=x_in, y=y_in;
Point.initialize(double x_in, double y_in) :x=x_in, y=y_in{ z = x+y;}
}
class A{
A({this.b});
A({Map<String, dynamic> data, path=“/”}) {}
A(string S){_S = S;};
// Getter (function=>return)
int _C;
int get C => _C;
// Setter (function=>set)
set C(num value) => _C=value
}
b. nullable 관련
- Null safety : Dart 2.12 버전 이후의 Null Safety가 적용되며 확인해주지 않으면 문제가 됩니다.
- 해결방법
- 변수 처리
- 선언시 바로 초기화를 해주는 방법
- 해당 변수를 Nullable 하게 만들게 "?"를 사용해 변수처리
- late 사용하기 ex) null 변수처리
- 입력 처리
- Nullable에 넣기 : value가 null 이면 어떻게 할지 "??"를 통해 정해줍니다.
ex) value = a.val1 ?? ”is null” - Nullable에 넣기2 : ternary conditional operator 사용합니다
ex) isValid ? 'valid' : 'invalid' - Non-Nullable에 넣기 : 넣을 때 확실히 보장하는 의미로 뒤쪽으로 "!"를 넣어주면 된다.
- Non-Nullable에 넣기2 : List혹은 Class의 경우 강제로 casting을 해줍니다.
ex) List<int> A = BCD.toList() as List<int>;
ex) VERSION? A = BCD as VERSION; 입력처리
- Nullable에 넣기 : value가 null 이면 어떻게 할지 "??"를 통해 정해줍니다.
- 함수 처리
- 함수에서 Non-Nullable인 경우 required로 확실히 넣어줄 것을 표시해주기
- 변수 처리
c. async 관련
- async(비동기)의 의미
- 시스템별로 비동기에 대해 사용하는 뜻이 다르지만, app개발 시는 어떤 동작이 완료되지 않아도, 다음동작을 수행하는 것이 비동기 입니다.
- 상황 예시(현재가 아닌 강제로 몇초 뒤에 들어오는 값) : Future<object> fut_obj = Future<object>.delayed
- 아래 예시 : 위를 함수 _future()로 구현했으며, return이 아닌 throw 100 등으로 사용하면 catchError로 분류 될 수 있습니다.
import 'dart:async'
Future<int> _future() {
return Future<int>.delayed(Duration(seconds: 5));
}
- Usage1 : then을 사용해 구현하는 방법
- 사용법 : fut_obj.then(성공시함수).catchError(실패시함수)
- 성공시 함수 : (val){val을 사용}
- 실패시 함수 : (error){error을 사용}
- 아래 예시의 경우 "Done"이 먼저 나온 뒤에 결과가 나옵니다.
- 사용법 : fut_obj.then(성공시함수).catchError(실패시함수)
import 'dart:async'
void function() {
Future<int> future = _future()
future.then((val){
print('val :$val');
}).catchError((error){
print('error: $error');
});
print('Done');
}
- Usage2 : async+await를 사용해 구현하는 방법
- async 함수 : Future<object>를 반환해야하며, await를 사용하기 위해서는 꼭 async 함수여야 합니다.
- await : async 함수 안에서 쓰인다.
- ex) final 오브젝트 = await 함수;
- await 하지않으면 ? 기다리지 않고 그냥 실행해 버립니다.
- then과 다른 점 : await 처럼 기다리지 않고, 동작이후에도 계속해서 실행하는 것이 다릅니다. 아래 결과 예시를 보면 알 수 있습니다.
- 아래 결과는 "This is future"이 프린트 된 뒤에 "Done"이 프린트 됩니다. 즉, ①_future함수 내용은 전부 실행이 되고, ②Future<int>를 반환 한 뒤에, ③future값이 완성될때까지 기다렸다가(await했을 때) ④"This is future"이 프린트되고 그다음에 ⑤"Done"이 실행되는 것입니다.
- ! 기다리는 것이 then 과 다릅니다.
import 'dart:async'
void function() async{
int future = await _future()
print("This is $future");
print('Done');
}
- Usage3 : FutureBuilder : async처리마다 표시 Widget반환 build
- future : 어떤 Future<object> 를 넣어주거나, 아래와 같이 함수를 넣어줍니다.
- builder(AsyncWidgetBuilder) : build할 함수
- BuildContext : 현재 context
- AsyncSnapshot : 현재 상태 확인
- snapshot.hasData : String나옴
- snapshot.hasData == false : 아무것도 안나옴 (Circular.. 로딩중표시)
- snapshot.hasError : 에러
...
child : FutureBuilder<String>{
future: _future(),
builder:(BuildContext context, AsyncSnapshot<String> snapshot){
if(snapshot.hasData){
return Text('Data ${snapshot.data.toString()}');
}else if(snapshot.hasError);
return Text('Error');
}else{
return Text('Waiting');
}
}
}
...
d. 변수 접근 방법
- 위젯 내의 변수 접근 : this.변수 혹은 그냥 변수로 접근 가능합니다.
- StatefulWidget 내 state 에서 widget 접근 : widget.변수로 접근 가능합니다.
- final값을 선언해 초기화를 꼭 해주어야합니다.
class ABCCLASS extends StatefulWidget {
final String? host;
ABCCLASS({this.host});
@override
_ABCCLASSState createState() => _ABCCLASSState();
}
class _ABCCLASSState extends State<ABCCLASS> {
final String? host_using;
@override
void initState() {
super.initState();
host_using = widget.host; // access
}
}
- 부모 context를 찾기 : .of(context)를 활용해 얻습니다.
- BuildContext : Widget tree에서 build로 만들어진 Widget의 위치를 의미하는 정보입니다. 즉, 위젯은 한가지 이상의 하위 위젯을 가질 수 있으며, 트리의 node가 되고 Widget 트리에서의 위치를 BuildContext라고 부른다.
- Build 예시: Widget build(BuildContext context) { return Scaffold();}
- Build 없이 만들어지는 위젯 대부분은 상위 위젯의 정보없이 만들어지지만, 강제로 상위 위젯의 Build Context가 필요한 경우도 있습니다. (아래 예시)
- 프로젝트 예 : 아래 코드 예에서 Scaffold는 BuildContext가 있지만, Center, RaisedButton, Text는 없습니다.
└── Scaffold
└── Center
└── RaisedButton
└── Text
- Scaffold.of(context) : 해당 context의 하위에서 상위로 부모 context를 찾아 Scaffold를 찾아 낸다
- 아래 Case1 : RaisedButton의 context(어떤것)의 하위에 Scaffold가 있기 때문에, 부모 중에는 Scaffold가 없습니다.
- 아래 Case2 : RaisedButton자체를 build했기 때문에, RaisedButton context의 상위에 Scaffold를 찾을 수 있습니다.
- BuildContext : Widget tree에서 build로 만들어진 Widget의 위치를 의미하는 정보입니다. 즉, 위젯은 한가지 이상의 하위 위젯을 가질 수 있으며, 트리의 node가 되고 Widget 트리에서의 위치를 BuildContext라고 부른다.
//Case1
Scaffold(
appbar : AppBar()
body : Center(
child :RaisedButton(
onPressed:(){
Scaffold.of(cotext).showSnackBar(
new SnackBar()
)
}
child : Text("Show!"),
)
)
)
//Case2
Scaffold(
appbar : AppBar()
body : Center(
child : Builder(
builder: (context) => RaisedButton(
onPressed:(){
Scaffold.of(cotext).showSnackBar(
new SnackBar()
)
}
child : Text("Show!"),
)
)
)
)
- 현재 context의 정보를 얻기 : MediaQuery.of() 등의 방법을 활용해 context의 정보를 얻어낼 수 있습니다.
- 개념
- Logical Pixel(LP) : 배율을 곱하기 전의 픽셀로, 안드로이드의 DP, DIP(Density-independent Pixel)와 ios의 PT(Points)와 같습니다. Flutter 에서 이 값을 기준으로 코딩할 수 있습니다.
- Physical Pixel : 배율을 곱한 후의 픽셀로 실제 디스플레이에 표현될 픽셀 입니다.
- Logical Pixel(LP) : 배율을 곱하기 전의 픽셀로, 안드로이드의 DP, DIP(Density-independent Pixel)와 ios의 PT(Points)와 같습니다. Flutter 에서 이 값을 기준으로 코딩할 수 있습니다.
- 첫번째 방법
- MediaQuery.of(context).size.height : 현재 context의 높이 정보
- MediaQuery.of(context).size : 앱 화면 크기 Size(360.0, 692.0)형태
- MediaQuery.of(context).size.height : 앱 화면 높이 double
- MediaQuery.of(context).size.width : 앱 화면 넓이 double
- MediaQuery.of(context).devicePixelRatio : 화면 배율
- MediaQuery.of(context).padding.top : 상단 상태 표시줄 높이 double
- MediaQuery.of(context).size.height : 현재 context의 높이 정보
- 두번째 방법
- import 'dart:ui';
- WidgetsBinding.instance.window.devicePixelRatio;
- WidgetsBinding.instance.window.physicalSize.width;
- WidgetsBinding.instance.window.physicalSize.height;
- WidgetsBinding.instance.window.padding.top;
- WidgetsBinding.instance.window.padding.bottom;
- WidgetsBinding.instance.window.textScaleFactor;
- 개념
- ModelView에서 Model에 접근할 때 : viewModel.value를 활용합니다.
e. Provider
- Provider : 동일한 상태(데이터)를 전역적으로 다른 위젯들과 공유할 때 사용합니다.
- 기존 문제점 : flutter의 구조상 Widget Tree내에 다른 depth에 있는데도 불구하고, 상태값을 공유하는 widget을 표현하기 위해서는 해당 상태값과 연결된 최하위 widget의 상태값이 변경되면서 최상위와 최하위 사이에 있는 모든 Widget들이 모두 다시 그려지게 됩니다. 이는 Overhead라고 볼 수 있습니다.
- Widget Tree와 상관없이 상태(데이터)를 저장할 클래스를 생성하고, 해당 상태를 공유하는 공통 부모 위젯에 Provider를 제공하기 위한 Provider를 만들 수 있습니다.
- MVVM에서의 Provider : 제공하는 쪽은 ViewModel로 구현하고 사용하는 쪽은 view로 구현하면 MVVM패턴이 됩니다. 즉, Provider는 Flutter에서 MVVM 패턴을 구현하기에 위해 필요한 도구입니다.
- 직접 구현시 예시
- import 'package:provider/provider.dart';
- Provider 선언체
- overriding : class 선언 시에 "with ChangeNotifier"나 "expends ChangeNotifer"를 해줍니다.
- notifyListeners() : 함수를 이용해 알려줍니다.
- Provider 사용체 : Provider선언체를 구독할 것
**기타 Flutter 관련 참조 사이트
1. DartPad : Dart를 테스트할 수 있는 무료 오픈소스 온라인 편집기
https://dartpad.dev/?null_safety=true
2. Pub.dev : Official 패키지 레포지토리
https://pub.dev
3. FlutterFlow : UI를 활용해 기본적인 레이아웃을 짤 수 있는 툴
https://bsscco.github.io/posts/2019-04-16-flutter-widget-hierarchy/
'Developers 공간 [Basic] > Frontend' 카테고리의 다른 글
[Web] React와 Typescript 시작하기 (0) | 2023.01.06 |
---|---|
[Web] Javascript 기초 문법 정리 (0) | 2022.12.21 |
[Web] HTML/CSS 기초 문법 정리 (0) | 2022.12.21 |