Dart 2.8.1 @ 2020.05.06

Flutter와 함께 2020년 처음으로 Dart의 공식 릴리즈도 발표 되었습니다. 2020년 5월 6일 발표된 2.8.1에 반영한 사항은 GitHub의 Dart SDK에서 확인 가능합니다 (참조: https://github.com/dart-lang/sdk/blob/master/CHANGELOG.md#281—2020-05-06). 공식 사이트에서 강조한 2.8.1의 가장 큰 변화는 non-nullable types에 대해서 향후 버전에 있을 사항에 대하여 미리 사전 준비를 한 것이라고 합니다. 이외에도 주요 코어 라이브러리에 대한 개선 작입이 이루어 졌습니다.

이 글에서는 Flutter와 마찬가지로 Michael Thomsen의 “Announcing Dart 2.8″의 주요 부분을 요약하고자 합니다 (출처: https://medium.com/dartlang/announcing-dart-2-8-7750918db0a).

Preparation for Null Safety

프로그램의 변수가 null 값을 가진 다는 것은 장점도 있지만, 매우 많은 에러와 버그의 원인이 되기도 합니다. Dart 언어는 이러한 null 값을 가지는 경우에 대한 안전성을 제공하기 위하여 노력해 왔습니다. null 값을 가지는 경우와 그래서는 안되는 경우에 대한 구분을 반영하기 위하여 Dart는 계속 개선 중이며, 이를 통해서 이런 타입의 변수들이 안정적으로 동작하고, null 값이 아닌 변수들에 대한 최적화된 코드 생성 등의 작업이 꾸준하게 이루어 지고 있습니다.

Null safety를 강화하기 위하여, Dart 언어와 라이브러리들에 큰 변화가 있었다고 합니다. dart:core, dart:async, dart:io에 중점적으로 변화가 있었으며, 이에 대해서는 https://github.com/dart-lang/sdk/issues/40686에서 보다 구체적으로 확인이 가능합니다.

Enhanced Package Ecosystem

가장 먼저 Dart/Flutter의 패키지를 관리하는 pub.dev 저장소의 성능 개선이 이루어 졌다고 합니다. 이를 위해서 pub outdated 명령이 새롭게 만들어 졌는데, 기 개발한 프로그램이 사용하는 패키지들의 의존성 점검 등을 효과적으로 진행하고, 업데이트를 제공합니다.

프로젝트에서 사용하는 package 들과 이들의 의존 관계를 정의한 pubspec.yaml 화일을 프로그래머가 만든후, pub get을 실행하면 puspec.lock 화일이 생성 됩니다. 이전의 문제는, 이후 pub upgrade를 하더라도 major 버전의 업그레이드는 이루어지지 않는다는 점 입니다. 이 부분에 대해서 pub outdated는 minor 버전의 업그레이드 가능성에 대한 분석과 함께 pubspec.yaml 화일의 수정에 대한 guideline을 제공하여 major 버전으로의 안정적인 업그레이드에 대한 가이드라인을 제공합니다.

예를 들어, 아래의 그림의 경우, pubspec.yaml에 foo는 1.3.0으로, bar는 2.0.1로 되어 있는 경우입니다. pub upgrade를 통해서 업그레이드가 가능한 버전은 각각 1.3.1과 2.1.0입니다. 하지만, bar의 경우 3.0.3의 버전이 가용한 것을 볼 수 있습니다. 이경우 pubspec.yaml의 dependencies를 ^3.0.3으로 변경 하고, pub upgrade를 수행하여, 최신 버전으로의 업그레이드 가능합니다.

[출처] https://medium.com/dartlang/announcing-dart-2-8-7750918db0a

pub get 명령에 대해서는 병렬로 fetch 하는 기능이 추가되어 성능 개선이 이루어 졌습니다. 이를 통해서, flutter create 후에 실행하는 실시한 flutter pub get의 속도가, Flutter 1.12 (Dart 2.7)에서는 6.5초가 걸리는 경우에 대해서, Flutter 1.17 (Dart 2.8)에서는 2.5초로 줄어 들었다고 합니다. Flutter가 제공하는 Flutter gallery 프로그램의 경우도 같은 작업에 15초가 걸렸지만, 이번 릴리즈에서는 3초로 줄어 들었다고 합니다.

마무리

Flutter 1.17.0에 이어서 Dart 2.8.1 릴리즈에 대해서 살펴 보았습니다. Flutter의 경우는 UI/UX에 대한 부분이 시선을 끌기에 그런 부분에 대한 개선이 확실하게 보이지만, Dart 언어는 언어 자체의 안전성과 이를 둘러싼 코어 도구들의 개선이 중점적으로 이루어 지고 있는 것을 볼 수 있습니다. 이렇듯 1년에 2~3회에 이루어 지는 릴리즈를 지속적으로 살펴보면, 어느 순간 Dart/Flutter의 성장과 함께 발전하는 개발자로서의 자신을 발견할 것 같습니다.

Flutter 1.17.0 @ 2020.05.07

Flutter 1.17.0 버전이 2020년 5월 7일 공식 릴리즈 되었습니다. 이번 릴리즈에 반영된 내용 전체는 Flutter 공식 사이트의 “Change log for Flutter 1.17.0 (출처: https://flutter.dev/docs/development/tools/sdk/release-notes/changelogs/changelog-1.17.0)”에서 확인 이 가능합니다. 이번 릴리즈에서는 231명의 contributor들의 총3164개의 PR(Pull Request)가 있는 것으로 발표 되어습니다.

공식 사이트에는 3164개의 항목이 줄줄이 나열될 뿐 category가 나뉘어지거나 중요도가 명시되지 않았기에, Chris Sells (참조: https://medium.com/@csells)가 작성한 “Announcing Flutter 1.17 (출처: https://medium.com/flutter/announcing-flutter-1-17-4182d8af7f8e)”를 중심으로 중요한 부분에 대해서 요점 정리를 해볼까 합니다. Chris Sells는 Product Manager, Flutter developer experience 로써, Flutter와 관련한 좋은 글들을 종종 발표해 주고 있습니다.

Enhanced App Size and Memory Utilization

1.17.0 릴리즈에서는 빠른 애니메이션, 작아진 어플리케이션 크기, 적은 메모리 사용율을 제공합니다. 이는 Navigation 시에 20~30%의 성능 개선, iOS 애니메이션 경우에 40% 수준의 CPU/GPU 사용 절감 등을 포함하고 있습니다.

어플리케이션 크기의 축소에 대해서 확인하면, Flutter에서 예제로 제공하는 Flutter Gallery 프로그램의 경우, 2019년 말 출시될 때에는 9.6MB 였지만, 1.17.0 버전에서는 18.5%가 축소되어 8.1MB가 되었다고 합니다. 메모리 효율 면에서는, 큰 이미지의 스크롤시에 70% 수준의 메모리 감소를 이루었다고 합니다. 이 향상은 기기의 메모리 용량에 따라 다른 차이를 보일 수 있습니다.

실제로 아래의 그림을 보면 성능 개선 이전과 이후의 메모리 사용 효율이 확연하게 차이가 날 만큼 개선된 것을 확인 할 수 있습니다.

[출처] Announcing Flutter 1.17, Chris Sells
(https://medium.com/flutter/announcing-flutter-1-17-4182d8af7f8e)

Metal Framework Performance Improvement

Apple의 Metal 프레임워크는 GPU에 거의 직접적으로 접근할 수 있는 기능을 제공하여 iOS, macOS 및 tvOS 앱의 그래픽과 컴퓨팅 잠재력을 극대화할 수 있습니다. 쉽게 접근할 수 있는 낮은 오버헤드의 아키텍처에 사전 컴파일된 GPU 셰이더, 세분화된 리소스 제어 및 멀티 스레딩 지원을 기반으로 하는 Metal은 GPU 기반 명령어를 생성하고, Metal 지원 GPU 배열 작업을 간소화하며 Mac Pro 및 Pro Display XDR의 Pro급 성능을 경험할 수 있도록 진화했습니다 (출처: https://developer.apple.com/kr/metal/).

Flutter 1.17.0은 iOS에서 Metal을 기본으로 사용하도록 되었습니다. 이를 통해서 평균 50% 수준의 렌더링 성능 개선 효과를 이루었다고 합니다. Metal을 제대로 지원하지 못하는 A7 프로세서 이전 기기 혹은 iOS 10 이전 운영체제에서는 Flutter의 이전 버전과 마찬가지로 OpenGL을 지원 합니다.

New Material Widgets & Text Scale

Material design에 포함되는 새로운 widget들이 추가 되었습니다. NavigationRail (참조: https://master-api.flutter.dev/flutter/material/NavigationRail-class.html), DatePicker (참조: https://api.flutter.dev/flutter/material/showDatePicker.html), 그리고 새로운 애니메이션 패키지 (참조: https://pub.dev/packages/animations)를 제공합니다.

1.17.0에서는 2018 Material Design specification의 Type Scale 부분에 대한 구현이 완료 되었습니다. 몇년간에 걸쳐서 이루어진 개발이지만, 과거 구현된 이름들등의 인터페이스가 변경되지 않고 유지 되었습니다. 따라서, 해당 기술은 1.17.0으로 소프트웨어를 업데이트 하면 기존 소스 프로그램의 수정 없이 반영이 됩니다.

Google Fonts Support

Google은 Flutter를 위한 Google Font를 2019년 12월에 열린 Flutter Interact에서 공개하였습니다 (참조: https://medium.com/flutter/introducing-google-fonts-for-flutter-v-1-0-0-c0e993617118). 1.17.0은 이를 반영한 최초의 릴리즈로서, “Google Fonts for Flutter v1.0 release (출처: https://pub.dev/packages/google_fonts 혹은 https://github.com/material-foundation/google-fonts-flutter)”를 지원하고 있습니다.

Flutter DevTools (Porting of Dart DevTools)

1.17.0에서는 기존 Dart DevTools의 Flutter 버전이 포함됩니다. Dart DevTools에 생긴 “비이커(beaker)” 아이콘을 통해서 활성화 가능합니다.

[출처] Announcing Flutter 1.17, Chris Sells
(https://medium.com/flutter/announcing-flutter-1-17-4182d8af7f8e)

Flutter 버전으로 바뀌면서 소소한 개선과 변화들이 있지만, 가장 큰 차이점은 Network 탭의 추가 입니다. 이 기능을 통해서 Flutter 어플리케이션이 사용한 네트워크 트래픽에 대한 리코딩이 가능합니다.

이외에 Android 앱 개발시 “fast start” 기능을 제공해서, 개발 단계에서 프로그램의 부분적인 수정시 APK를 re-build 하는 부담을 줄입니다. Android 앱 개발시 기존 Android Studio Library를 사용하던 방식에서 AndroidX/Android-Jetpack를 사용하는 방식으로 바뀌기도 했습니다. Android Studio나 IntelliJ 사용시 코드의 analysis error가 Hot Reload 기능을 비활성화 하던 부분도 개선이 이루어 졌습니다.

마무리

2020년 들어서 처음 출시된 Flutter 릴리즈인 1.17.0에 대해서 Chris Sells의 “Announcing Flutter 1.17″에서 주요 부분을 요약하는 입장에서 정리해 보았습니다. Tutorial을 마쳤어도, 소프트웨어 기술을 살아 움직이는 생물처럼 계속 진화와 변화가 있으므로, 정기적인 릴리즈에 대한 지속적인 관심과 이해는 필수라고 볼 수 있습니다.

Dart Programmer 되기 [47]

< Wrap-up >

Dart 언어에 대한 글을 작성하기 시작한 후, 벌써 47번째 글 입니다. 처음 생각했던 아젠다들을 모두 채우고 마지막으로 Wrap-up을 하게 되었습니다. 마지막 글은 알아두면 좋은 사항과 이후 관심이 있으면 조금 더 자세하게 공부하기를 권장하는 내용을 소개하고자 합니다.

Official & 3rd Tools

개발 도구는 생산성을 궂은 일들을 자동화하거나 발생할 오류를 사전에 알려줘서 피할 수 있는 등 생산성을 향상하고 신뢰성을 증가시키는 부분에서 매우 중요합니다. 우리는 Visual Code를 대표적인 Editor로 사용하였고, 그외 필수적으로 사용해야할 만한 도구들을 필요할 때마다 소개하고 사용하였습니다. 하지만 Tutorial 인 입장에서 디버그와 자동화 부분에 대해서는 다루지 못한 것이 사실 입니다. 그리고 오픈소스 도구를 권하고나, 상용 소프트웨어의 경우는 소개를 하지 않은 부분도 사실 입니다. 여기서는 Dart와 Flutter의 디버그에 도움이 될 만한 도구들, 그리고 상용 이지만 학교에 소속하거나 학생이면 무료로 경험할 수 있는 도구들을 중심으로 소개를 하려합니다. 기회가 된다면 꼭 한번 즈음을 경험하기를 바랍니다.

첫번째 도구는 Chrome DevTools 입니다. 이 도구는 Dart 전용 도구는 아닙니다. Google Chrome 브라우저에 내장되어 있는 Web 저작 및 디버깅 도구 입니다. Chrome 브라우저에 내장되어 있기에, 별도의 프로그램이나 Plugin을 설치할 필요가 없고, HTML/CSS/JavaScript에 대한 디버그와 성능 측정을 하는 부분에서 많이 사용됩니다. (참조: https://developers.google.com/web/tools/chrome-devtools)

기기모드를 통해서 다양한 형태의 기기들에서 어떻게 컨텐츠가 보여지는지 확인 가능합니다. Element 패널에서는 DOM/CSS를 자유롭게 조작하고 사이트의 레이아웃/디자인을 실험하는 것이 가능합니다. Console 패널을 통해서는 JavaScript의 수행 동작 내용을 확인할 수 있습니다. Source 패널에서는 JavaScript의 브레이크 포인트 설정 등의 작업을 통해서, 원하는 지점에서의 수행 내용을 확인하는 등의 작업이 가능합니다. Network 패널에서는 접속하는 통신 내용에 대한 확인과 페이지내 요소들의 로딩 성능 등을 측정하는 것이 가능합니다. Performance 패널은 사이트의 라이프사이클 동안 발생한 다양한 이벤트를 기록하고 탐색하여 페이지의 런타임 성능을 향상합니다. Memory 패널에서는 메모리 사용 행태를 확인하고 누수를 확인 할 수 있습니다. 그리고 JavaScript의 CPU 프로파일링도 가능합니다. Application 패널은 IndexedDB와 웹 SQL 데이터베이스, 로컬 및 세션 스토리지, 쿠키, 어플리케이션 캐시, 이미지, 폰트, 스타일시트를 포함한 로딩된 모든 리소스를 검사합니다. 마지막으로 Security 패널에서는 인증서 문제 및 mixed content 등의 보안 이슈를 디버깅 할 수 있습니다.

두번째 도구는 Dart SDK 안에 포함된 도구들 입니다. 이전의 글에서 등장했던 dart, dart2js, dart2native, dartdevc, pub 외에도 디버그와 성능개선을 위한 도구들이 제공되고 있습니다 (참조: https://dart.dev/tools/sdk). dartaotruntime은 AOT-compile된 소프트웨어를 실행하기 위한 명령입니다. dartanalyzer는 정적 분석을 실행합니다. dartdoc은 API 문서화를 자동으로 수행 하는 도구 입니다. dartfmt는 Dart 언어로 만든 코드의 형태를 자동으로 조정해 줍니다.

세번째 도구는 현재 프리뷰 상태인, DevTools 입니다. 이는 Dart 언어 및 Flutter로 만든 프로그램의 성능과 디버그를 위한 도구들의 집합 입니다. Android Studio, IntelliJ, Visual Code, CLI 프로그램에 설치 가능 합니다. 제공하는 기능은 다음과 같습니다 (참조: https://flutter.dev/docs/development/tools/devtools/overview ).

  • Inspect the UI layout and state of a Flutter app.
  • Diagnose UI jank performance issues in a Flutter app.
  • Source-level debugging of a Flutter or Dart command-line app.
  • Debug memory issues in a Flutter or Dart command-line app.
  • View general log and diagnostics information about a running Flutter or Dart command-line app.

네번째 도구는 상용 소프트웨어인 JetBrains의 제품입니다. Dart 언어의 Plugin을 지원하는 IntelliJ 및 Web 기능시 함께 사용할 수 있는 WebStrom 등 유용한 도구들이 많이 제공되고 있습니다. 상용 소프트웨어를 굳이 설명하는 이유는 학생 및 교사인 경우 무료로 사용 가능하기 때문입니다. 혹은 오픈소스 프로젝트, 신생 기업, 교실/강의 지원 등에도 무료 사용이 가능하니 조건이 되는 독자의 경우는 경험해 보기 바랍니다 (참조: https://www.jetbrains.com/ko-kr/store/?fromNavMenu#discounts?billing=yearly).

다섯번째 도구는 웹 기반 Dart 및 Flutter 개발 환경 입니다. 당초 이런 용도를 위해서는 DartPad가 있습니다 (참조: https://dartpad.dev/). DartPad는 간단하게 Web 브라우저를 통해서 프로그램을 타이핑하고 실행하기 위한 용도로 되어 있습니다. 여기에 추가로 웹 기반 프로그래밍 언어 개발, 온라인 강좌 제공 및 코드 공유를 지원하는 CodePen이 최근 Flutter on CodePen이라는 이름을 통해서 Flutter 기반 프로그램에 대한 지원을 시작 하였습니다 (참조: https://codepen.io/flutter). Google Material Design 등 사용자 인터페이스의 개발과 실험이 가능하고, 각종 Widget도 사용이 가능하니 간편하게 Web 기반 개발/시험 환경이 필요한 경우는 요긴하게 사용하기 바랍니다. (참조: Announcing CodePen support for Flutter)

More about Flutter for Web

Dart와 Flutter를 통한 Web 어플리케이션 개발은 앞서 여러 회에 걸쳐서 다루었습니다. 하지만 Flutter를 사용하는 방향으로 진행을 했기에, 기존에 HTML/CSS/JavaScript에 대한 지식을 갖추고 있거나 혹은 이를 개발해 온 개발자라면 아쉬움이 있었을 겁니다. 이런 아쉬움을 달래줄 설명이 공식 사이트의 “Flutter for web developers (출처: https://flutter.dev/docs/get-started/flutter-for/web-devs)” 입니다. 이 글에는 자주 사용되는 HTML/CSS 구문(syntax, code snippets)에 상응하는 Dart/Flutter 코드를 1:1로 대응해서 보여줍니다.

그리고 “Understanding constraints (출처: https://flutter.dev/docs/development/ui/layout/constraints)”는 Dart/Flutter를 통해서 Web을 개발하는 경우의 제약 사항이나 주의점을 설명하고 있습니다. HTML/CSS에서 하고자 했던 부분이 Dart/Flutter에서 안되거나 하는 일이 발생하면 참조하여 이해하기 바랍니다.

이외에도 블로그나 기사들에서 Dart/Flutter를 사용하여 Web 어플리케이션을 만드는 경우에 대한 좋을 글을 제공하고 있습니다. 아래는 대표적인 글들을 모아 보았으니, 관심이 있는 독자는 읽어 보기 바랍니다.

“Developing Web Apps Using Flutter”
“Flutter For Web: A Complete Guide to Create & Run a Web Application”
“Flutter for the Web”
“Flutter Boosts Web Support, Defaults to Swift, Kotlin for Native Mobile”
“Responsive Web And Desktop Development With Flutter”

Other Language Integration

Dart 언어는 플랫폼 별로 깊이가 있는 프로그래밍이 가능하도록 하기 위해서, Dart 언어가 아닌 언어와의 연동이 가능하도록 합니다. 지원하는 언어는 C, C++, Java, Kotlin, Swift, Objective-C 입니다. C/C++는 Foreign Function Interface 라고 불리는 dart:ffi 라이브러리를 통해서 가능합니다. 이는 C/C++로 만들어진 native 환경에 대한 기술을 연결하기 위한 목적으로 사용합니다 (참조: https://flutter.dev/docs/development/platform-integration/c-interop).

iOS의 native 개발은 Swift/Objective-C로 이루어 지고, Android의 native 개발은 Java/Kotlin 입니다. 이를 연동하기 위한 platform-specific code 기능은 Dart 처럼 플랫폼 독립적인 언어의 단점을 보완하고자 native의 기능을 직접 연결하도록 지원합니다 (참조: https://flutter.dev/docs/development/platform-integration/platform-channels).

Fuchsia OS Support

Fuchsia는 Google이 만들고 있는 새로운 운영체제 입니다. Zircon이라는 microkernel을 기반으로 만들어 지고 있으며, 사실 기술을 공개가 되고 있지만, 정확하게 어디에 사용지 될지에 대한 부분에 대해서는 정보가 많지 않습니다. Android 운영체제의 계승자라고도 하고 임베디드 기기를 위한 운영체제라고도 하는 등 명확하게 설명이 되어있는 자료는 미흡합니다. 이러한 가운데 이 새로운 운영체제를 위한 언어로서 C/C++과 Dart/Flutter가 선택 되었고, Rust/Go/Python이 탈락한 것으로 알려졌습니다. 그리고 Fuchsia 사이트에는 이에 대한 설명이 있습니다 (출처: https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/docs/project/policy/programming_languages.md).

Fuchsia OS가 언제 제대로 출시되어 어떤 분야에서 자리를 잡을지 알기는 어렵지만, 새로운 기술에 대한 흥미가 높고, Dart/Flutter의 한계가 어디까지 인지 궁금한 경우는, 심심풀이 삼아 공부와 실험을 해보시기 권합니다 (출처: https://fuchsia.dev/fuchsia-src/development/languages/dart)

Flutter for Embedded Devices

다양한 플랫폼에 Dart/Flutter가 도입되는 것을 보았습니다. 아직 공식적으로 임베디드 기기들에 특화된 기술이 소개되고 있지는 않지만, Dart/Flutter의 마지막 도전으로 예상되는 부분은 임베디드 기기로의 적용 입니다. 최근 Raspberry Pi와 같은 소규모 장치에서 Flutter를 실현하는 데모들이 발표되는 사례가 있습니다. 이러한 추이로, 임베디드 기기들에 Dart/Flutter가 도입되는 시나리오에 대한 논의가 시도되고 있습니다. 특히 2019년 Google I/O에서 Google Home Hub와 같은 기능을 제공하는 스마트 디스플레이 등에 Flutter가 도입된 것으로 알려지면서, 이에 대한 기대가 더욱 커지고 있는 상황 입니다 (참조: https://developers-kr.googleblog.com/2019/05/Flutter-io19.html).

마무리

일주일에 하나 정도의 글을 포스팅 하는 것을 목적으로 하였는데, 근 1년치의 글을 마치게 되었습니다. 지금까지의 글을 통해서 Dart/Flutter가 할 수 있는 전방위적인 분야에 대해서 두루 살펴보고자 하는 목적은 어느 정도 이룬 것으로 보입니다. Dart/Flutter는 모바일 어플리케이션에서 급성장하고 있으며, 이에 맞추어 기술 개발도 매우 빠른 속도로 이루어 지고 있는 것으로 보입니다. 지금까지의 tutorial을 충실하게 진행해 왔다면, 이제는 스스로 말드고자 하는 어플리케이션을 정하고, 해당 분야 깊숙하게 기술 개발을 수행하는 단계로 나아가는 일만 남았습니다. 취미로 시작하는 Dart/Flutter가 개발자로서의 본인 역량 강화와 의도하지 않은 좋은 미래로 이끄는 계기가 되기를 바랍니다.

Creative Commons License (CC BY-NC-ND)

Dart Programmer 되기 [46]

< Container 개발 – Dart HTTP Server over Docker, Part.2 >

이번 글에서는 Dart 언어로 만든 서버 프로그램을 Container 기술에 적용하는 경우에 Part.1에서 다룬 HTTP 서버에 특화된 google/dart-runtime 베이스 이미지가 아닌, google/dart 베이스 이미지를 사용하는 방법으로 접근하도록 하겠습니다. 앞서 Part.1과는 독립적으로 다루기 위해서, 완전히 새로운 프로젝트를 열고 작업하는 것으로 가정합니다.

[1단계] Stagehand를 통한 Start Application의 생성

작업을 수행할 적절한 디렉토리를 만든 후, 다음의 명령으로 간단한 console 어플리케이션의 템플리트가 만들어 지도록 합니다. 저의 경우는 dart4docker 라는 디렉토리에 다음 명령을 수행 하였습니다.

stagehand console-simple

[2단계] main.dart 프로그램의 변경

다음 단계는 bin 디렉토리 하단의 main.dart 프로그램을 Docker 상에서 container로 동작할 HTTP 서버 프로그램으로 변경하는 일 입니다. 이를 위해서, 작업 폴더를 Visual Code로 엽니다. 이번 글에서는 새로운 HTTP 서버를 만들기 보다는, 이미 우리가 tutorial을 진행하면서 만든 HTTP 서버 프로그램을 재사용 합니다.

앞서 27번 글에서 만든 darttutorial-27-02.dart 프로그램의 내용을 복사하여, main.dart의 내용을 덮어 쓰도록 합니다.

이 프로그램은 Docker container 환경에서 그대로 사용할 수 없습니다. 이유는 main()의 HOST 값 설정이 InternetAddress.loopbackIPv4로 되어 있는데, 이는 같은 컴퓨터 안에서의 접속만 허용함으로, Docker container의 경우는 OS레벨의 가상화를 지원하는 일종의 논리적인 별도의 컴퓨터에서 동작하므로, 이 값을 InternetAddress.anyIPv4로 변경하여, 외부에서의 접속을 허가하도록 수정합니다.

[3단계] Dockerfile 생성

Dockerfile은 container image를 생성하기 위한 화일로, Docker를 다뤄본 사람이라면 당연하게 알고 있는 화일 입니다. 작업 폴더의 루트 위치에 Dockerfile을 생성합니다.

[4단계] google/dart 도커 이미지의 이해

Dockerfile의 내용을 작성하는 단계에서, Google이 제공하는 dart 이미지를 이해할 필요가 있습니다. Docker Hub에 등록(https://hub.docker.com/r/google/dart)된 이 이미지는 Dart 언어로 만들어진 어플리케이션을 Docker 상에서 개발하기 위한 베이스 이미지 입니다. 앞서 우리가 사용한 dart-runtime 이미지의 베이스 이미지 이며, Google이 만들고 배포한 이미지 입니다.

[5단계 Dockerfile 작성

개발자가 직접 Dockerfile의 내용을 상세하게 작성해야 합니다. Dokcer Hub의 이미지 설명 내용에 Dockerfile의 Sample이 있으며, 이를 토대로 우리가 만드는 프로그램의 Dockerfile을 다음과 같이 작성하였습니다.

FROM google/dart

WORKDIR /app

ADD pubspec.* /app/
RUN pub get
ADD . /app
RUN pub get --offline

CMD []
ENTRYPOINT ["/usr/bin/dart", "/app/bin/main.dart"]

[6단계] Container Image의 빌드

Docker container image의 build는 일반적인 절차대로 수행하면 됩니다. 다음은 이를 수행하는 명령을 예시로 적었습니다.

docker build –tag dartserver:1.0 .

Docker를 사용해 본 경험이 있는 경우 바로 이해 가능하겠지만, dartserver 라는 이름의 이미지로 버전을 1.0으로 주는 형태로 태그를 만들었습니다.

[7단계] Container Image의 실행

이제 Docker를 통해서 image를 실행할 단계 입니다. 실행은 일반적인 container의 실행과 다르지 않습니다. 다음의 명령은 이의 예시 입니다.

docker run –name mydartserver -d -p 4041:4040 dartserver:1.0

dartserver:1.0 이미지에서 실행하는 컨테이너의 이름은 mydartserver로 했으며, 통신 포트 매핑을 localhost:4041이 container의 4040에 대응 하도록 하였습니다.

[8단계] Container 동작 확인

27번 글의 실행처럼 Web 브라우저를 통한 접속도 정상적으로 동작하며, Docker를 다룬다면 보다 친숙한 다음의 명령으로 정상 동작을 확인 합니다.

curl localhost:4041

결과는 27번 글에서와 동일하게 아래와 같은 문구가 출력될 것 입니다 (시간 정보는 실행에 따라 다름).

2020-05-09 05:09:32.141864: Hello World!

마무리

Google이 Dart 언어로 만든 프로그램을 Docker 위에서 구동할 수 있는 두개의 베이스 이미지를 제공하는 덕분에 Dart 언어로 만든 서버 프로그램의 컨테이너 기반 구동이 매우 용이하게 되었습니다.

HTTP 프로토콜을 사용하는 서버 프로그램이라면 dart-runtime 베이스 이미지를 권장하며, 포트 번호에 제약 등이 싫은 경우는 dart 베이스 이미지를 사용하는 것이 적합해 보입니다. 이 글을 작성하는 시점에도, 이러한 사항을 반영하는지 dart 베이스 이미지를 다운로드한 건수가 dart-runtime 베이스 이미지를 다운로드한 건수 대비 2배 정도인 것을 확인 할 수 있었습니다. 하지만 dart 베이스 이미지를 사용하는 경우는, Dockerfile 등을 작성하는 등에서 직접 작성해야 하는 불편함이 일부 존재 합니다.

그리고 본 글에서는 Stagehand를 기반으로 template를 생성하고, 이를 토대로 프로젝트를 개발하였습니다. 하지만, 직접 만든 화일들 만으로 Dart 어플리케이션을 만드는 것도 언제든지 가능합니다. 이런 경우는 Dockerfile에서 yaml 화일 및 pub 기반 패키지 제어에 대해서 손수 직접 작성해 주면 되므로, 일부 번거러울 뿐 문제는 없으니 참조하기 바랍니다.

Creative Commons License (CC BY-NC-ND)

Dart Programmer 되기 [45]

< Container 개발 – Dart HTTP Server over Docker, Part.1 >

이번 글에서는 Dart 언어로 만든 서버 프로그램을 Container 기술에 적용하는 방법을 다루도록 하겠습니다. Container 기술은 Docker를 사용할 예정이며, 이를 위해서 독자는 Docker를 포함한 Conainer 기술에 대한 이해가 필요합니다.

[1단계] Stagehand를 통한 Start Application의 생성

작업을 수행할 적절한 디렉토리를 만든 후, 다음의 명령으로 간단한 console 어플리케이션의 템플리트가 만들어 지도록 합니다. 저의 경우는 dartserver 라는 디렉토리에 다음 명령을 수행 하였습니다.

stagehand console-simple

[2단계] main.dart 프로그램의 변경

다음 단계는 bin 디렉토리 하단의 main.dart 프로그램을 Docker 상에서 container로 동작할 HTTP 서버 프로그램으로 변경하는 일 입니다. 이를 위해서, 작업 폴더를 Visual Code로 엽니다. 이번 글에서는 새로운 HTTP 서버를 만들기 보다는, 이미 우리가 tutorial을 진행하면서 만든 HTTP 서버 프로그램을 재사용 합니다.

앞서 27번 글에서 만든 darttutorial-27-02.dart 프로그램의 내용을 복사하여, main.dart의 내용을 덮어 쓰도록 합니다.

이 프로그램은 Docker container 환경에서 그대로 사용할 수 없습니다. 이유는 main()의 HOST 값 설정이 InternetAddress.loopbackIPv4로 되어 있는데, 이는 같은 컴퓨터 안에서의 접속만 허용함으로, Docker container의 경우는 OS레벨의 가상화를 지원하는 일종의 논리적인 별도의 컴퓨터에서 동작하므로, 이 값을 InternetAddress.anyIPv4로 변경하여, 외부에서의 접속을 허가하도록 수정합니다. 그리고 PORT 값 설정을 4040에서 8080으로 변경합니다. 이에 대한 이유는 추후 설명 하도록 하겠습니다.

[3단계] Dcokerfile 작성

Dockerfile은 container image를 생성하기 위한 화일로, Docker를 다뤄본 사람이라면 당연하게 알고 있는 화일 입니다. 작업 폴더의 루트 위치에 Dockerfile을 생성합니다.

[4단계] google/dart-runtime 도커 이미지의 이해

Dockerfile의 내용을 작성하는 단계에서, Google이 제공하는 dart-runtime 이미지를 이해할 필요가 있습니다. Docker Hub에 등록(https://hub.docker.com/r/google/dart-runtime)된 이 이미지는 Dart 언어로 만들어진 어플리케이션을 Docker 상에서 개발하기 위한 베이스 이미지 입니다. 특별히 Web 서버와 같이 8080 류의 통신 포트를 통해서 외부와 통신하는 경우를 위하여, Google이 만들고 배포한 이미지 입니다.

이번 글에서 우리는 이 이미지를 베이스 이미지로 사용할 것인데, 다음의 4가지 전제 조건을 충족하는 경우에만 사용할 수 있습니다.

  • pubspec.yaml 화일을 포함하여, 화일간 의존성을 명시할 수 있을 것
  • 실행할 프로그램이 bin/server.dart로 제공되어 entry point로 사용할 수 있을 것
  • 8080 통신 포트를 통해서 외부 요청을 받을 것
  • Container 생성시 프로그램이 의존하는 모든 패키지의 접근이 가능할 것

첫번째 조건은 Stagehand를 통한 프로젝트 생성시 자동으로 pubspec.yaml 화일이 생성되어 만족 합니다. 두번째 조건을 위해서는 bin/main.dart 프로그램의 이름을 bin/server.dart로 수정할 필요가 있습니다. 세번째 조건을 만족시키기 위하여, 2단계에서 4040인 포트 번호를 8080으로 변경했습니다. 네번째 조건은 접근 불가한 패키지가 없으니 현 상태에서 만족 합니다.

이렇게 조건을 만족한 경우, Dockerfile에는 다음의 한줄만 들어가면 됩니다.

FROM google/dart-runtime

[5단계] Container Image의 빌드

Docker container image의 build는 일반적인 절차대로 수행하면 됩니다. 다음은 이를 수행하는 명령을 예시로 적었습니다.

docker build –tag dartserver:1.0 .

Docker를 사용해 본 경험이 있는 경우 바로 이해 가능하겠지만, dartserver 라는 이름의 이미지로 버전을 1.0으로 주는 형태로 태그를 만들었습니다.

[6단계] Container Image의 실행

이제 Docker를 통해서 image를 실행할 단계 입니다. 실행은 일반적인 container의 실행과 다르지 않습니다. 다음의 명령은 이의 예시 입니다.

docker run –name mydartserver -d -p 8081:8080 dartserver:1.0

dartserver:1.0 이미지에서 실행하는 컨테이너의 이름은 mydartserver로 했으며, 통신 포트 매핑을 localhost:8081이 container의 8080에 대응 하도록 하였습니다.

[7단계] Container 동작 확인

27번 글의 실행처럼 Web 브라우저를 통한 접속도 정상적으로 동작하며, Docker를 다룬다면 보다 친숙한 다음의 명령으로 정상 동작을 확인 합니다.

curl localhost:8081

결과는 27번 글에서와 동일하게 아래와 같은 문구가 출력될 것 입니다 (시간 정보는 실행에 따라 다름).

2020-05-09 04:41:43.079079: Hello World!

마무리

Dart 언어를 통하여 서버 프로그램을 만드는 것은 아직 활성화 되지 않아 보이지만, 언어가 가지는 매력으로 많은 개발자들이 관심을 기울이니, 서버에서의 인기도 조만간 많이 늘어날 것으로 보입니다. 특히 Google에서 Docker를 위한 Dart 기반 서버의 베이스 이미지를 제공하는 덕에 조금 더 수월하게 HTTP 기반의 서버를 구동할 수 있다는 것을 이번 글에서 느낄수 있습니다. 다음 글에서는 Google에서 제공하는 또 다른 Docker용 베이스 이미지를 다루어 보겠습니다.

Creative Commons License (CC BY-NC-ND)

Dart Programmer 되기 [44]

< Desktop 개발 – Flutter for Desktop >

본 글은 2020년 5월 9일에 https://github.com/flutter/flutter/wiki/Desktop-shells 사이트를 토대로 작성 되었습니다. 아직 기술의 변화가 큰 부분이므로, 해당 사이트를 직접 방문하여 최신의 내용을 확인하기를 권장 합니다.

앞서 Flutter를 통한 모바일 어플리케이션과 Flutter for Web을 통해서, 하나의 Flutter 기반 프로그램이 소스 코드의 수정 없이 스마트폰/테블릿과 Web 브라우저에서 동일하게 동작하는 것을 확인 하였습니다. 같은 방법으로 Desktop용 어플리케이션을 Flutter를 통해서 개발할 수 있습니다. Flutter로 Desktop을 지원하는 기술은 아직 공식 릴리즈 단계는 아니며, 개발이 진행중인 상황입니다. 궁극적으로 macOS, Windows, Linux를 지원하는 것을 목표로 하고 있습니다. Flutter for Desktop은 GitHub의 Flutter 공식 사이트에서 Desktop shells로 설명되고 있습니다. Desktop운영체제 별로 개발 수준에 차이가 있는데 각각의 운영체제별 상황은 다음과 같습니다.

macOS

macOS는 가장 진도가 많이 나간 것으로 되어 있습니다. alpha 버전에 진입한 상태이며, https://flutter.dev/desktop 사이트를 통해서 설치, 프로젝트 생성 및 환경 설정, 프로젝트 실행, App Store를 통한 배포시 주의사항, macOS를 지원하는 Plugin 상황, macOS용 Plugin 제작방법 및 Sample 들이 제공되고 있습니다.

특히 flutter create 명령이 fully support 되는 것으로 되어 있습니다. 따라서 이 글은 macOS에서 Flutter for Desktop을 개발하는 환경을 기반으로 설명 하겠습니다.

Windows

현재 early technical preview 상태 입니다. Win32 버전으로 되어 있으며, UWP (Universal Windows Platform – Windows 10에 처음 도입된 MS사 제품에 대한 Universal[= Cross] Platform) 버전으로의 진화를 계확하고 있다고 합니다. 현재의 API 들에서 많은 변화가 있을 것으로 설명하고 있습니다.

특히 flutter create 명령시에 동작은 하지만, 안정화되지 않았다고 합니다. 따라서, Flutter를 업데이트 한 후에는, windows 디렉토리를 반드시 삭제하고 재생성할 필요가 있는 등의 후속 작업이 필요하다고 합니다.

Linux

현재 early experiment가 가능한 상태 입니다. GLFW(Graphics Library Framework : OpenGL을 위한 오픈소스 멀티 플랫폼 라이브러리) 기반으로 만들어져 있으며, 향후 다른 기술로 변경될 예정입니다. 다양한 플랫폼을 지원하는 기술을 선택하기를 희망하여 기술의 선택을 검토중이라고 하며, GTK+가 현재 후보라고 합니다. 현재 사용한 API은 대부분 변경될 예정이라고 하니, 현재 버전을 사용하는 경우에는 주의가 필요합니다.

특히 flutter create 명령시에 동작은 하지만, 안정화되지 않았다고 합니다. 따라서, Windows와 동일하게, Flutter를 업데이트 한 후에는, linux 디렉토리를 반드시 삭제하고 재생성할 필요가 있는 등의 후속 작업이 필요하다고 합니다.

Plugins

Desktop 운영체제를 지원하는 Plugins의 갯수가 많지도 않지만, Desktop 운영체제별로 구현 수준이 서로 다르기에 플랫폼 독립적인 구동도 기대하기 어렵습니다. 특히 Windows와 Linux의 경우는 아직 시험적인 수준이기에, Plugin을 개발하더라도 pub.dev를 통한 배포는 권장하지 않고 있으니, Desktop을 위한 Plugin을 개발하거나 사용하는 경우는 주의하기 바랍니다.

“Hello, World!” Flutter for Desktop

Flutter for Desktop을 사용하는 방법은 앞서 Flutter for Web에서 경험한 것처럼, 소스 코드 레벨에서의 수정이 아닌 개발 환경에서의 변경으로 가능합니다. 일반 Flutter 프로젝트를 만들고 실행하는 것과 같은 맥락으로 진행하며, 다음과 같이 하여 최초의 Flutter for macOS 프로그램을 만들고 실행해 보겠습니다 ( 참조: https://flutter.dev/desktop ).

첫번째 단계는 Flutter for Desktop 환경으로 전환하기 위해서, 다음 명령을 수행합니다. 이는 Flutter for Desktop이 아직 개발 단계이기에, Flutter channel을 master로 바꾸는 작업과, 필요한 기능들을 platform 안에 설치하는 단계 입니다. 향후 Flutter for Desktop의 공식 릴리즈가 이루어 지면, 없어지거나 달라질 부분 입니다.

flutter channel master
flutter upgrade

두번째 작업은 Flutter for Desktop 환경을 macOS 환경에 맞도록 설정하는 단계로 다음의 명령을 실행합니다. Windows 운영체제는 –enable-windows-desktop 옵션을 주고, Linux 운영체제는 –enable-linux-desktop 옵션을 주면 됩니다. 이 부분은 아마도 공식 릴리즈가 나온 후에도 동일한 작업일 것으로 예상합니다.

flutter config –enable-macos-desktop

세번째 작업은 제대로 동작이 이루어 지는지 확인하는 단계로서, 다음의 세가지 명령을 실행해서 결과를 확인 합니다.

flutter devices
flutter doctor
flutter config

두 가지 명령은 앞서에서 여러번 등장했습니다. 즉 flutter devices는 Flutter를 다양한 플랫폼에서 동작하는 경우, 현재 실행 가능한 플랫폼을 확인하는 명령으로서, 앞서 flutter config –enable-macos-desktop로 인하여, [그림 1]과 같이 macOS가 실행 가능한 플랫폼으로 등록된 것을 볼 수 있습니다.

[그림 1] flutter devices 실행 화면

flutter doctor는 Flutter 개발 및 실행 환경에 대한 점검을 하는 기능으로 여러번 등장 했습니다. [그림 2]와 같이 master 채널로 설정되고, 3개의 디바이스가 연결되었으며, 이슈가 없는 것을 볼 수 있습니다.

[그림 2] flutter doctor 실행 화면

마지막으로 flutter config –enable-macos-desktop의 경우는 macOS용 환경을 활성화 하기 위한 용도 였지만, [그림 3]과 같이 그냥 flutter config 만 실행하면, 사용법을 설명한 후 맨 마지막에 enable-macos-desktop 설정이 true가 된 것을 확인 할 수 있습니다.

[그림 3] flutter config 실행 화면

다음 과정은 일반적인 Flutter 프로젝트를 만들고 실행하는 부분과 동일합니다. 특히 Flutter for Web과 같은 방식으로 구동하게 됩니다.

작업을 희망하는 디렉토리에서 다음 명령을 실행하여 프로젝트를 생성합니다.

flutter create myapp

그리고 myapp 디렉토리로 이동하여 (cd myapp), 다음의 명령으로 실행합니다. 이를 통해서 debug 모드에서 앞서에서 여러번 등장한 Flutter의 Start 어플리케이션이 [그림 4]와 같이 동작하는 것을 볼 수 있습니다. “+” 버튼을 누르면 숫자가 업데이트 되는 동작도 동일합니다. 따라서 소스 코드 레벨에서의 수정 없이 동일한 Start 어플리케이션을 Desktop 환경에서도 구동할 수 있는 것을 보았습니다.

flutter run -d macos

[그림 4] Flutter Start App을 macOS Desktop에서 실행한 모습

만역 debug 용도가 아닌 build 용도라면, 다음의 명령을 수행하면 됩니다.

flutter build macos

Flutter Mobile App을 Desktop App으로 실행하기

이번에는 스마트폰을 위해서 개발한 Flutter 기반 어플리케이션을 macOS Desktop 환경에서 실행해 보겠습니다. 앞서 35번째 글인 “Flutter 활용하기 – Skeleton Program for Future Usage”에서 완성한 main.dart로 [그림 4]에 해당하는 myapp의 main.dart를 갱신 합니다. 그리고 프로그램을 다시 실행하면 [그림 5]와 같이 macOS에서 실행하는 프로그램을 만날 수 있습니다.

[그림 5] Flutter 기반 Mobile Application을 macOS Desktop에서 실행한 모습

만약 “Flutter 활용하기 – Skeleton Program for Future Usage”에서 완성한 프로젝트 폴더를 보관하고 있다면, 단지 폴더안에서 다음의 명령을 실행하는 것으로 macOS Desktop에서의 실행 환경을 마칠 수 있습니다.

flutter create .

마무리

Flutter for Desktop이 아직 공식 릴리즈가 아니고, Desktop 운영체제별로 상황이 판이하지만, 어떤 기술인지와 현재 수준에 대해서 알아보았습니다. 그리고 Flutter로 만든 스마트폰 어플리케이션을 Web 어플리케이션으로 실행한 경우와 거의 동일하게, 다시 macOS Desktop 환경에서 실행해 보았습니다. 결국 하나의 소스 프로그램이 플랫폼과 상관없이 동일한 도구와 함수들로 구현되도록 하는 것이 Flutter의 철학이라는 것을 이해하게 됩니다.

하지만, Flutter를 사용하는 입장에서는 이렇게 간단하게 이야기 할 수 있지만, 이렇게 하기 위한 노력은 용이하지 않으며, Desktop 운영체제별로 꽤 오랜 시간이 지나야 안정적인 환경이 제공될 수 있으리라 생각합니다. 하지만 충분히 가능성이 있는 기술인 만큼, 꾸준한 관심과 학습이 있다면, ONE SOURCE MULTI PLATFORM 이라는 철학을 구현하는 가장 핫한 기술로 나름 의미있는 효율성과 생산성을 보장할 것으로 봅니다.

Creative Commons License (CC BY-NC-ND)

Dart Programmer 되기 [43]

< Web 개발 – AngularDart for Web >

결론부터 이야기 하면, AngularDart는 기존의 Angular 프레임워크를 이해하는 개발자에게 권장하기에 적합한 기술 입니다. Angular를 다뤄보지 않은 경우라면, Flutter 기반으로 개발을 하거나, Angular에 대한 이해를 한후 AngularDart를 다루는 것을 권합니다.

Dart 언어로 Web 어플리케이션을 만드는 방법으로 지금까지 두가지 방법을 설명 하였습니다. 첫째는 Flutter를 사용하는 방법으로, HTML/CSS에 대한 이해 보다는 Flutter 프레임워크로 모바일 프로그래밍 방식과 동일하게 Web 어플리케이션을 만드는 방법입니다. 두번째는 Dart for Web으로 dart:html 코어 라이브러리를 사용하여, HTML/CSS/JavaScript를 사용하는 방식에서 JavaScript 대신 Dart 언어가 사용되는 형태였습니다. 마지막은 Web 개발시 사용되는 Angular 프레임워크를 Dart 언어로 porting한 AnduglarDart 입니다. JavaScript로 개발된 Angular 프레임워크에 대응하는 Dart의 프레임워크라고 보면 됩니다. 공식 홈페이지는 https://angulardart.dev/ 입니다.

AngularDart는 현재 공식 릴리즈는 아니고, alpha 버전 입니다. Google은 Angular에 의존하는 미션 크리티컬 앱이 계속 제대로 작동하는지 확인하기 위해서, 각 버전의 AngularDart (alpha 릴리스 포함)를 철저히 테스트 하고 있다고 공표하고 있습니다. alpha 레이블은 API가 변경될 가능성이 있으며, 앞으로 나올 릴리스 (또는 이후 릴리스)로 인해 코드가 손상 될 수 있음도 경고하고 있으니 참조 바랍니다.

일단 가장 간단한 AngularDart 기반의 프로그램을 돌려보도록 하겠습니다. 이를 위해서, 공식 홈페이지의 Get Started를 방문( https://angulardart.dev/guide/setup )해 봅니다. 공식 버전이 릴리즈 되지 않은 이유인지, 여러 방법이 제시되고 있는데, 여기서는 가장 간단한 방법을 사용해 보고자 합니다.

첫번째로 AngularDart 기반의 프로그램의 기본 골격이 만들어져 있는 프로젝트의 압축 화일을 다운로드 받습니다. Get Started에 공개된 링크를 통해서도 다운로드 가능하지만, GitHub에서 다음 주소의 화일로 바로 다운로드 가능합니다. zip으로 압축된 화일이므로, 압축을 해제한 후, 희망하는 디렉토리에 위치시킵니다.

https://github.com/angular-examples/quickstart/archive/master.zip

두번째로 압축이 해제된 디렉토리 안으로 이동해서 CLI 커맨드로 pub get 명령을 수행하여, AngularDart를 포함한 패키지 화일을 다운 받아 설치할 수 있도록 합니다.

세번째로 webdev serve 명령으로 바로 실행을 해보도록 합니다. 그리고 http://localhost:8080으로 접속을 합니다. 이에 따른, 웹 브라우저 상에 단순히 아래와 같은 “Hello Angular” 문자열을 출력하는 기능입니다.

소스 코드를 살펴보면, 먼저 index.html의 body 부분이 다음처럼 되어 있는 것을 볼 수 있습니다. 특이하게 <my-app> 태그가 있는 것을 볼 수 있습니다.

<body>
  <my-app>Loading...</my-app>
</body>

다음으로 main.dart는 다음과 같이 짧게 되어 있습니다. import를 통해서 AngulartDart 패키지와 이에 기반하는 어플리케이션의 template를 불러 오는 것을 볼 수 있습니다.

import 'package:angular/angular.dart';
import 'package:angular_app/app_component.template.dart' as ng;

void main() {
  runApp(ng.AppComponentNgFactory);
}

사실 위의 두개 화일로는 왜 화면에 “Hello Angular”가 출력되었는지 이해를 하기 어렵습니다. 이 부분을 이해하기 위해서는 압축을 해제한 프로젝트 폴더안의 lib 서브 디렉토리에 위치한 app_component.dart 화일을 열어봐야 합니다. 이 화일의 내용은 다음과 같습니다.

import 'package:angular/angular.dart';
@Component(
   selector: 'my-app',
   template: 'Hello {{name}}',
)
class AppComponent {
   var name = 'Angular';
}

AngularDart 어플리케이션은 components 들로 구성 됩니다. component는 HTML template와 component class의 조합으로, 웹 브라우저의 스크린에 나타날 내용을 제어하는 역할을 합니다. 주어진 예제를 토대로 설명하면, component는 모두 @Component 키워드로 시작 합니다. component 안의 selector 문법을 볼 수 있는데, 이 부분이 index.html에서 <my-app> 태그로 되어 있는 부분에 대해서, 어떻게 출력이 되는지를 결정합니다. 주어진 예제에서는 “my-app” 태그 부분을 제어하는 내용으로 ‘<h1>Hello {{name}}</h1>’를 사용한다는 의미에서 template 문법이 사용되는 것을 볼 수 있습니다. 정리해서 이야기 하면, index.html의 <my-app>을 웹 브라우저가 처리해야 하면, app_component.dart에서 “my-app”을 selector로 지정한 component를 찾는 것 입니다. 이렇게 해서, 매칭되는 component를 찾으면 정해진 내용으로 index.html의 해당 부분을 업데이트 하는데, 현재는 ‘<h1>Hello {{name}}</h1>’ 내용으로 출력을 하는 것 입니다. 이는 HTML 구문인데, 이슈는 {{ … }}으로 되어진 부분입니다.

이런 문법을 AngularDart에서는 interpolation binding 표현이라고 부릅니다. 어플리케이션 실행시, AngularDart는 {{name}}으로 되어진 부분을 component의 name Property의 값으로 대체하는데, 위의 코드를 보면, 이 값이 ‘Angular’ 문자열 인 것을 볼 수 있습니다. 따라서, 화면에 “Hello Angular” 문자열이 출력되게 됩니다. 따라서 var name = ‘Angular’; 구문을 var name = ‘World’;으로 변경해 봅니다. 그리고 나서 다시 화면을 업데이트 하면 다음과 같이 출력이 바뀐 것을 알 수 있습니다.

우리가 실행한 위의 프로그램을 공식 사이트에서는 “Starter App”이라고 하며, 공식 사이트인 https://angulardart.dev/tutorial/toh-pt0 에서 더 세부적인 설명을 볼 수 있습니다.

마무리

이 Tutorial에서는 Flutter를 주로 다루는 것을 방향으로 하고 있기에, AngularDart에 대한 소개는 이 정도로 마칩니다. AngularDart는 Angular 프레임워크를 선호하는 개발자라면 관심을 기울여 볼만한 기술입니다. Angular에 대한 지식을 갖춘 개발자라면 공식 사이트의 Tutorial( https://angulardart.dev/tutorial )을 따라해 보면서, 기존 Angular와 같은 부분과 다른 부분에 대해서 이해해 보도록 하기 바랍니다.

주의할 사항은 아직 공식 릴리즈가 아닌 만큼 업데이트 되는 상황을 수시로 모니터링 하면서 기술을 활용할 필요가 있습니다.

Creative Commons License (CC BY-NC-ND)

Dart Programmer 되기 [42]

< Web 개발 – Dart for Web >

이 글에서 설명하는 기술은 Flutter를 사용하지 않고, Dart언어의 Core 라이브러리인 dart:html을 사용하는 방법 입니다. 이는 HTML/CSS/JavaScript의 조합에서 JavaScript에 상응하는 역할을 Dart 언어 그리고 dart:html의 코어 라이브러리 기능이 수행 한다고 보면 됩니다. 따라서, 이 글을 이해하기 위해서는 HTML/CSS/JavaScript를 기반으로 Web 어플리케이션을 만드는 기술에 대한 기본적인 이해가 필요합니다.

“Hello, World!” Dart web-only app 이해

앞서 < Web 개발 – “Hello, World!” Dart web-only app >에서 Stagehand가 자동으로 생성한 소스 코드를 살펴보도록 합니다. 먼저 index.html 입니다.

<!DOCTYPE html>

<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="scaffolded-by" content="https://github.com/dart-lang/stagehand">
    <title>dart4web</title>
    <link rel="stylesheet" href="styles.css">
    <link rel="icon" href="favicon.ico">
    <script defer src="main.dart.js"></script>
</head>

<body>

  <div id="output"></div>

</body>
</html>

head 부분은 일반적인 내용으로 채워지고 있습니다. script가 main.dart.js로 설정되어 dart 소스 프로그램이 JavaScript로 변환되어 실행된다는 것을 볼 수 있습니다. 그리고 JavaScript에서 HTML 컨텐츠를 동적으로 다룰때 사용하는 전형적인 형태로서, id를 가진 영역을 선언하였으나, 처음에는 비어 있는 것을 볼 수 있습니다. 여기서 우리가 동적으로 컨텐츠를 업데이트할 id는 “output” 입니다.

그러면, 이 “output”라는 id를 갖는 영역을 업데이트 하는 Dart 코드를 볼 차례입니다. 해당 코드는 main.dart에 정의되어 있습니다.

import 'dart:html';

void main() {
  querySelector('#output').text = 'Your Dart app is running.';
}

가장 먼저 Dart 언어의 HTML 제어 코어 라리브러리인 dart:html을 import하는 것을 볼 수 있습니다. 그리고 main()가 선언되어 있습니다. main()안에는 한줄의 코드가 있습니다. 기능은 html 화인에서 “output”을 id로 갖는 구문을 찾은후(querySelector()), 그 속에 있는 문자열을 “Your Dart app is running.”로 바꾼다는 의미입니다.

따라서 이 프로그램이 수행이 되면, 비어있는 영역에 상기 문자열을 동적으로 삽입하는 것을 볼 수 있습니다. 여기서 알수 있듯이, HTML과 JavaScript가 연결되는 프로그래밍 방식과 거의 동일한 방식으로 HTML과 Dart 소스 프로그램이 연결되어 있는 것을 볼 수 있습니다. 따라서, JavaScript에서 HTML을 제어하는 방식에 상응하는 함수들이 dart:html에서 제공이 되고 있다는 것을 예측할 있습니다.

마지막 확인인 css 화일은 styles.css 로서, 다음의 내용을 갖습니다.

@import url(https://fonts.googleapis.com/css?family=Roboto);

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: 'Roboto', sans-serif;
}

#output {
  padding: 20px;
  text-align: center;
}

CSS에 대한 기본 지식이 있다면, 특별히 어렵거나 인상적인 코드는 아니며, 일반적인 형태와 폰트에 대한 부분인 것을 알 수 있습니다.

dart:html을 통한 HTML 제어

dart:html을 사용하여 html로 표시되는 Web 컨텐츠를 동적으로 변경하는 내용에 대해서 조금 더 이해를 해 보겠습니다. 이를 위해서, main.dart의 내용을 다음과 같이 변경 합니다. 이는 Dart의 공식 사이트( https://dart.dev/tutorials/web/get-started )에 게재된 내용을 그대로 사용합니다.

import 'dart:html';

Iterable<String> thingsTodo() sync* {
  var actions = ['Walk', 'Wash', 'Feed'];
  var pets = ['cats', 'dogs'];

  for (var action in actions) {
    for (var pet in pets) {
      if (pet == 'cats' && action != 'Feed') continue;
      yield '$action the $pet';
    }
  }
}

void addTodoItem(String item) {
  print(item);

  var listElement = LIElement();
  listElement.text = item;
  todoList.children.add(listElement);
}

UListElement todoList;

void main() {
  todoList = querySelector('#todolist');
  thingsTodo().forEach(addTodoItem);
}

다음으로 index.html 화일도 body 부분을 변경한 다음의 내용으로 변경 합니다.

<!DOCTYPE html>

<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="scaffolded-by" content="https://github.com/dart-lang/stagehand">
    <title>dart4web</title>
    <link rel="stylesheet" href="styles.css">
    <link rel="icon" href="favicon.ico">
    <script defer src="main.dart.js"></script>
</head>

<body>
    <h2>A Simple To-Do List</h2>
    <p>Things to do:</p>
    <ul id="todolist">
    </ul>
</body>
</html>

먼저 index.html 화일의 body 내용을 살펴보면, 헤더(h2)로 제목을 나타냅니다. 그리고 문장(p)으로 “Things to do:”를 문자열을 화면에 출력합니다. 그리고 id를 “todolist”로 정의한 비어 있는 Unnumbered-List를 생성하는 것을 볼 수 있습니다. 앞서 Hello World 예제처럼 이 부분의 내용을 Dart 언어를 사용하여 동적으로 변경할 것 입니다. 이 프로그램을 webdev serve 명령으로 수행하고, http://localhost:8080으로 접속하면 다음과 같이 Web 컨텐츠가 채워지는 것을 볼 수 있습니다.

이렇게 화면이 채워지게된 이유를 main.dart를 통해서 설명 하겠습니다. main.dart 안에는 main()을 제외하고 두개의 함수가 있습니다.

addTodoItem() 함수는 입력 파라메타도 문자열을 하나 받습니다. 그리고 디버그 용도를 위해서 print() 함수로 화면 출력을 수행합니다. 이 출력은 Web 브라우저를 개발자 모드로 수행해서, JavaScript 프로그램의 출력이 표시되는 console 탭을 선택하면 확인 가능 합니다. 다음의 3줄이 수행해야 하는 동작인데, 먼저 비어있는 List Element를 하나 생성합니다. 그리고, 이 List Element의 text 내용을 입력 파라메타로 받은 내용으로 채우게 됩니다. 이렇게 채워진 문자열은 HTML 화일인 index.html의 body 부분인 todolist id 부분을 채우기 위한 용도로 사용되어, 함수 실행시 마다 List Element를 하나씩 추가 합니다. 이를 통해서 위의 화면 출력과 같이 4개의 Unnumbered List 4개가 나타나도록 합니다.

thingsTodo() 함수가 실질적으로 HTML에 추가되는 4개의 Unnumbered List Element를 만들어 내는 기능을 합니다. 이 함수 안에는 동작을 의미하는 문자열 3개로 이루어진 actions 리스트와 동물을 나타내는 pets 리스트가 있습니다. Nested for 구문을 통해서, 이들을 매칭하게 되며, pets가 dogs인 경우는 3가지 actions에 모두 매핑하여, “$action the $pet”의 문자열을 생성합니다. pets가 cats 인 경우는 오직 actions이 “Feed”인 경우메나 문자열을 생성합니다. 여기서 yield 구문은 return 구문과 유사하게 값을 리턴하는 문법 입니다. 차이점은 return이 한번에 하나의 값을 전달하고 마친다면, yield 구문은 generator 구문으로서, thingsTodo()의 결과 값이 필요한 구문이 실행될때 마다 그때 그때 필요한 결과 값을 만들어서 리턴하는 점에서 다릅니다. 이를 thingsTodo() 함수가 async* 구문의 비동기적인 동작을 하는 부분에서 기인 합니다.

이 두가지 함수를 이해한 상태에서 main() 함수를 보면, 이해가 용이합니다. 즉 main()의 첫번째 줄에서는 index.html에서 정의한 비어있는 영역을 선택하게 되고, 여기에 thingsTodo()가 실행되면서 만들어지는 하나 하나의 문자열을 추가하게 되는 것 입니다.

따라서, HTML/CSS/JavaScript를 혼용하여 Web 콘텐츠를 다룰수 있는 개발자라면, JavaScript 대신 Dart 언어와 dart:html 코어 라이브러리를 사용하여 Web 콘텐츠를 제어하는 것이 가능 합니다.

결론적으로 Dart 언어로 DOM을 사용하려면, JavaScript를 통한 Web 컨텐츠를 다루는 경우와 동일하게, Window, Document, Element 및 Node 에 대해 알아야합니다.

Window 객체는 웹 브라우저의 실제 윈도우를 나타냅니다. 각 Window에는 현재 로드 된 문서를 가리키는 Document 객체가 있습니다. Window 객체에는 IndexedDB (데이터 저장 용), requestAnimationFrame (애니메이션 용) 등과 같은 다양한 API에 대한 접근자가 있습니다. 탭 브라우저에서 각 탭에는 고유한 Window 객체가 있습니다.

Document 객체를 사용하면 문서 내에서 Element 객체를 만들고 조작 할 수 있습니다. Document 자체는 요소이며 조작 할 수 있습니다.

DOM은 Node 트리를 모델링 합니다. 이러한 Node는 종종 Element 이지만 attributes, test, comment 및 기타 DOM 유형일 수도 있습니다. 부모가 없는 루트 노드를 제외하고 DOM의 각 노드에는 하나의 부모가 있으며 많은 자식이 있을 수 있습니다.

마무리

이 글에서는 전통적인 Web 어플리케이션을 만드는 기술인 HTML/CSS/JavaScript의 조합을 사용하는 기술에서 JavaScript 대신 Dart를 활용하는 접근 방법인 Dart for Web 기술에 대해서 알아 보았습니다.

dart:html을 사용한 Dart for Web 기술에 대해서 좀 더 이해하고 싶다면, Dart 공식 사이트에서 “A tour of the core libraries”의 dart:html에 대한 설명인 “dart:html – browser-based apps” 부분( https://dart.dev/guides/libraries/library-tour#darthtml )을 읽고 실행해 보기를 권장합니다.

Creative Commons License (CC BY-NC-ND)

Dart Programmer 되기 [41]

< Web 개발 – Flutter for Web : Login UI Example >

지금까지 Flutter for Web를 개발하기 위하여, 기술의 연혁과 도구 설치, 그리고 간단한 예제 프로그램을 실행하고 이해하여 보았습니다. 앞서 dart2js 등에서 본 것처럼, Flutter for Web은 HTML/CSS/JavaScript와 같은 표준 웹 기술을 사용하여 Web 콘텐츠를 개발하도록 지원 함으로써, Dart로 만든 프로그램이 어떠한 Web 브라우저 상에서도 동작하도록 합니다.

Flutter for Web은 Dart 언어로 만들어진 프로그램이 JavaScript로 변환되는 단계에서 더 나아가서, Flutter 프레임워크가 제공하는 다양한 core drawing layer가 기존 Web 브라우저 위에서 동작 할 수 있도록, [그림 1]과 같이, Dart/Flutter Framework를 표준 Web 브라우저 API 상에서 구현하는 작업을 수행하였습니다.

[그림 1] Flutter for Web 기본 개념 (출처: https://flutter.dev/web )

앞서의 글에서, 현재 Flutter for Web이 기존 Web 기술로 만들어진 Web 사이트들을 모두 바꾸려 하기 보다는 인터액티브하고 동적인 콘텐츠를 표현하는 Web 어플리케이션을 목표로 한다고 하였습니다. Flutter for Web 공식 사이트( https://flutter.dev/web )에는 이에 대해서 다음의 세가지 시나리오를 예를 들어서 설명하고 있습니다.

A connected Progressive Web Application built with Flutter : Flutter로 만들어진 기존의 모바일 어플리케이션을 보다 다양한 device들에 적용하거나, 아니면 Web 상에서도 같은 경험을 제공하려는 경우

Embedded Interactive content : 기존 Web 페이지 안에서도 쉽게 운용이 가능한 rich & data-centric 기능을 적용하는 경우로서, 예를 들어 데이터 시각화, 자동차 구성 요소 설정과 같은 온라인 도구, embedded된 형태의 차트 등의 embedded web content를 개발하는 경우

Embedding dynamic content in a Flutter mobile app : 기존 모바일 애플리케이션 내에서 동적 컨텐츠를 업데이트 하기 위해서 사용하는 일반적인 방법은 동적으로 컨텐츠를 로드 할 수 있는 Web view control 이였지만, Flutter는 웹 및 모바일 컨텐츠를 위한 통합 환경을 제공하므로, 컨텐츠를 플랫폼에 따라서 다시 작성하지 않고도 앱에 내장 할 수 있음

따라서, Flutter for Web의 사용에는, 기존의 모든 Web 기술을 대체하겠다는 접근 보다는, 목적에 맞는 사용이 필요합니다. 예를 들어 블로그 기사와 같은, 텍스트 중심의 정적인 컨텐츠를 제공하는 경우는 기존 Web 기반 기술을 활용하고, app-centric한 서비스에서 interactive하고 data-centric한 UI 프레임워크가 동적으로 동작하는 것이 필요한 경우가 Flutter for Web을 도입하기 좋은 환경인 것으로 권장하고 있습니다.

이제, Web 환경을 고려한 Flutter 기반 첫번째 어플리케이션을 만들어 보겠습니다. 주 내용은 Flutter 공식 사이트( https://flutter.dev/docs/get-started/codelab-web )의 “Write your first Flutter app on the web”을 기반으로 진행하고자 합니다. 동작은 단순 합니다. [그림 2]와 같은 로그인 화면을 통해서 사용자로부터 이름과 사용자 아이디를 입력 받은 후, 환영 인사 문구를 화면에 보여주는 정도입니다.

[그림 2] First Flutter app in the web
(출처: https://flutter.dev/docs/get-started/codelab-web )

좀 더 세부적으로 기능을 살펴보면, 화면에는 이름, 성 및 사용자 이름의 세 가지 텍스트 필드가 있습니다. 사용자가 필드를 채우면 진행률 표시 줄이 로그인 영역의 상단을 따라 움직입니다. 세 개의 필드가 모두 채워지면 진행률 표시 줄이 로그인 영역의 전체 너비를 따라 녹색으로 표시되고 가입 버튼이 활성화됩니다. 가입 버튼을 클릭하면 시작 화면이 화면 하단에서 애니메이션으로 표시됩니다.

실행에 앞서서 flutter doctor를 실행해서, 개발 환경에 문제가 없는지 다시 한번 확인하고, flutter devices를 통해서 Chrome과 Web Server가 Flutter for Web의 실행을 위해서 연결되어 있는지 확인 합니다.

Step.0 New Flutter Project with Text Input Fields

Flutter for Web을 경험할 새로운 Flutter 프로젝트를 생성합니다. 이를 위해서 본인이 작업할 디렉토리로 이동하여 flutter create 명령을 실행합니다. 아래는 myapp 이라는 이름으로 Flutter 프로젝트를 생성한 경우 입니다.

flutter create myapp

다음은 Flutter 공식 홈페이지에서 가져온 초기 단계 코드인 darttutorial-41-main-step-0.dart 프로그램으로 myapp 프로젝트의 main.dart( myapp/lib/main.dart ) 프로그램을 대체 합니다.

// Source: https://flutter.dev/docs/get-started/codelab-web
// darttutorial-41-main-step-0.dart

 import 'package:flutter/material.dart';
 void main() => runApp(LoginApp());
 class LoginApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       routes: {
         '/': (context) => LoginScreen(),
       },
     );
   }
 }
 class LoginScreen extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       backgroundColor: Colors.grey[200],
       body: Center(
         child: SizedBox(
           width: 400,
           child: Card(
             child: LoginForm(),
           ),
         ),
       ),
     );
   }
 }
 class LoginForm extends StatefulWidget {
   @override
   _LoginFormState createState() => _LoginFormState();
 }
 class _LoginFormState extends State {
   final _firstNameTextController = TextEditingController();
   final _lastNameTextController = TextEditingController();
   final _usernameTextController = TextEditingController();
 double _formProgress = 0;
 @override
   Widget build(BuildContext context) {
     return Form(
       child: Column(
         mainAxisSize: MainAxisSize.min,
         children: [
           LinearProgressIndicator(value: _formProgress),
           Text('Sign Up', style: Theme
               .of(context)
               .textTheme
               .display1), // display1 changes to headline4 in 1.16
           Padding(
             padding: EdgeInsets.all(8.0),
             child: TextFormField(
               controller: _firstNameTextController,
               decoration: InputDecoration(hintText: 'First name'),
             ),
           ),
           Padding(
             padding: EdgeInsets.all(8.0),
             child: TextFormField(
               controller: _lastNameTextController,
               decoration: InputDecoration(hintText: 'Last name'),
             ),
           ),
           Padding(
             padding: EdgeInsets.all(8.0),
             child: TextFormField(
               controller: _usernameTextController,
               decoration: InputDecoration(hintText: 'Username'),
             ),
           ),
           FlatButton(
             color: Colors.blue,
             textColor: Colors.white,
             onPressed: null,
             child: Text('Sign up'),
           ),
         ],
       ),
     );
   }
 }

그리고 다음의 명령을 통해서 실행해 봅니다. 지금까지의 작업을 잘 따라왔다면, 문제없이 Chrome 브라우저를 통해서 이름과 아이디를 입력하도록 하는 화면이 열렸을 겁니다.

flutter run -d Chrome

Flutter 공식 홈페이지에서는 바로 수정 작업을 진행하지만, 본 글에서는 앞서 main.dart의 내용을 좀 더 살펴 보도록 하겠습니다. 편의상 main.dart의 설명을 용이하게 하기 위하여 줄 번호가 포함된 형태인 [그림 3]과 [그림 4]로 나누어서 이해 하도록 합니다.

[그림 3] main.dart 이해 (1/2)

3번 줄에서 이 프로그램이 Material 디자인을 기반으로 만들어 지는 것을 알 수 있습니다.

5번 줄에서는 main() 실행 시, LoginApp() 객체를 만들어서, 프로그램이 처음 구동하도록 합니다.

7번 줄에서는 LoginApp 클래스의 정의가 StatelessWidget를 확장하여 만들어 지는 것을 볼 수 있습니다. 내부 구성은 앞서 Flutter에서 다뤘던 내용과 동일하게 build()를 override하는 것을 볼 수 있습니다. MaterialApp() 객체를 리턴 하는 것 까지는 이전에 다룬 전형적인 형태인데, 내부를 보면 지금까지 다루지 않았던 routes Property를 설정하는 내용이 나옵니다. 여기서 MaterialApp 클래스의 routes Property에 대해서 이해가 필요합니다.

MaterialApp 클래스의 routes Property는 Map<String, WidgetBuilder> 타입으로 정의되어 있습니다 (출처: https://api.flutter.dev/flutter/material/MaterialApp/routes.html ). String에 해당하는 문자열과 이 문자열에 매핑 되는 Widget을 생성할 Builder 함수를 입력 파라메타로 받는 다는 것 입니다. 일반적인 Web 사이트 접속 시 사용하는 URL(웹 사이트 주소)을 연상하면 쉽게 이해할 수 있습니다. 예를 들어, test.net 이라는 Web 사이트에 접속할 때, Web 브라우저의 주소 창에 다음처럼 입력을 합니다.

http://test.net

이렇게 하면, 통상 test.net 사이트를 동작하고 있는 Web 서버 컴퓨터에서 Web 서버 프로그램의 root 디렉토리의 index.html 화일을 동작하게 됩니다. Root 디렉토리를 기호로 “/”로 나타낸다는 것을 이해한다면, 12번 줄의 의미가 바로 이해될지 모르겠습니다.

12번 줄은 사용자가 root 디렉토리(“/”)로 접속하면 LoginScreen() 객체를 생성하여, 해당 객체의 생성시 수행하는 작업을 진행하라는 의미 입니다. Routes Property가 Map 타입인 것을 상기 한다면, 여러 개의 입력 값에 반응 가능한 복수의 기능을 정의할 수 있는 것을 예측 할 수 있습니다. 따라서, [그림 3]에서는 root 디렉토리(“/”)에 대한 처리만 정의 했지만, 만약 root 하단의 sub1 디렉토리에 대한 요청과 root 하단의 sub2 디렉토리에 대한 동작을 추가로 정의하고 싶은 경우는 다음과 같이 각각에 functionSub1()과 functionSub2()를 수행하도록 정의할 수 있습니다.

routes: {
‘/’: (context) => LoginScreen(),
‘/sub1’: (context) => functionSub1(),                   
‘/sub2’: (context) => functionSub2(),
}

12번 줄에서 최초 root 로의 접근시 실행하는 LoginScreen()은 18번 줄부터 정의되어 있습니다. 이는 StatelessWidget으로 확장하여 만듭니다. 그리고 전형적인 실행 예제처럼 build()를 override하는 것을 볼 수 있습니다.

21번 줄을 보면 Scaffold()를 return하는 것을 볼 수 있는데, 22번 줄을 보면 배경 색깔을 grey[200]으로 정의하는 것과, body()를 포함하도록 합니다.

23번 줄을 보면, body()에 Center()를 생성하는데, 이 안을 보면, LoginForm()을 child로 포함하는 SizedBox()를 하나 포함하는 것을 볼 수 있습니다.

[그림 4] main.dart 이해 (2/2)

다음은 main.dart의 반에 해당하는 내용인 [그림 4]를 설명하도록 하겠습니다.

35번 줄에서는 LoginForm이 StatefulWidget()을 확장해서 만들어 지는 것을 볼 수 있습니다. 그리고 내부 상태로 LoginFormState를 createState()로 생성하는 것을 볼 수 있습니다.

40번 줄부터 LoginFormState()가 State<>의 확장으로 만들어 지는 것을 볼 수 있습니다.

41번 줄에서 43번 줄을 통해서, 3개의 TextController()가 내부 상태로 만들어 집니다. 이들은 TextEditingController()로 만들어 집니다. 여기서 처음 등장한 TextEditingController 클래스에 대해서 좀 더 알아 보겠습니다.

TextEditingController 클래스는 편집 가능한 텍스트 필드에 대한 컨트롤러 입니다(참조: https://api.flutter.dev/flutter/widgets/TextEditingController-class.html ). 이 클래스에서 주요한 property는 value, text 그리고 selection 입니다. 먼저 value Property는 TextEditingController 안에 저장되어 있는 현재의 값입니다. text Property는 사용자가 편집하고 있는 현재 문자열을 의미합니다. selection Property는 현재 선택된 텍스트를 의미합니다.

사용자가 관련 TextEditingController로 텍스트 필드를 수정할 때마다 텍스트 필드는 value Property를 업데이트하고 컨트롤러는 해당 Listeners에게 알립니다. 그러면 Listeners는 text Property 및 selection Property를 읽고 사용자가 입력 한 내용 또는 selection이 어떻게 업데이트 되었는지 알 수 있습니다. 마찬가지로 text Property 또는 selection Property를 수정하면 텍스트 필드에 알리고 적절하게 업데이트 됩니다. TextEditingController를 사용하여 텍스트 필드의 초기 값을 제공 할 수도 있습니다. 이미 text Property가 있는 컨트롤러로 텍스트 필드를 작성하면 텍스트 필드는 해당 텍스트를 초기 값으로 사용합니다. 이 컨트롤러에 추가 된 Listeners 내에서 text Property 또는 selection Property를 설정할 수 있습니다. 두 속성을 모두 변경해야하는 경우 컨트롤러 value Property를 대신 설정해야 합니다. 더 이상 필요하지 않은 경우 TextEditingController의 dispose Method를 실행 합니다. 이를 통해 객체가 사용하는 모든 리소스를 폐기 할 수 있습니다.

48번 줄을 보면, 이렇게 3가지 편집 가능한 텍스트 컨트롤러를 상태 값으로 관리하는 LoginFormState 클래스는 build 함수에서 Form() 객체를 리턴 합니다.

49번 줄을 보면, Form 클래스는 복수의 form field widget들을 묶어서 그룹으로 만든 container를 만듭니다.

50번 줄을 보면, Form 클래스의 구성 요소는 Column 형태로 묶인, LinearProgressIndicator(), Text(), 그리고 3개의 Padding()와 FlatButton()인 것을 알 수 있습니다. Text()는 입력 창의 맨 위에 표시한 “Sign Up” 글자를 나타내며, FlatButton()은 아직 활성화가 안된 (onpressed Property가 null인) “Sign Up” 글자를 내용으로 가지는 버튼입니다.

58번 줄을 포함한 3개의 Padding()은 각각 TextFormField()를 가지고 있는데, 이 안의 controller Property를 보면, 앞서 정의한 StatefulWidget의 상태 값인 TextEditingController()로 설정되어 있는 것을 볼 수 있습니다. Padding Property와 decoration Property는 각각 사용자 화면의 구성과 힌트 문자열을 보여주는 효과를 나타냅니다.

Step.1 Add welcome screen

이제 사용자가 텍스트의 입력을 마치면, 화면의 맨 아래 위치한 “Sign up” 단추를 눌러서 작업을 마치는 기능을 추가 하도록 합니다. 이를 위해서, main.dart 프로그램을 수정합니다. 먼저, 화면 중간에 간단하게 “Welcome!” 이라는 글자를 출력하는 WelcomeScreen 클래스를 위한 아래 코드를 추가 합니다.

class WelcomeScreen extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       body: Center(
         child: Text('Welcome!', style: Theme.of(context).textTheme.display3),
       ),
     );
   }
 }

해당 함수는 앞서의 MaterialApp의 routes Property를 통해서 접근 가능하도록 수정할 계획입니다. 따라서, 아래의 문장을 “/”에 대한 구문 아래 추가 합니다.

'/welcome': (context) => WelcomeScreen(),

이제 “Sign up” 단추를 활성화 하고, 이 단추가 눌렸을 때, 프로그램이 “/welcome” route로 이동하도록 해야 합니다. 이 기능을 위해서, LoginFormState 클래스 내부 method로, 아래의 showWelcomeScreen() 함수를 추가 합니다.

void _showWelcomeScreen() {
   Navigator.of(context).pushNamed('/welcome');
 }

Flutter는 단 하나의 Navigator 객체를 갖습니다. 이 위젯은 스택 내에서 Flutter의 화면 (routes 또는 pages 라고도 함)을 관리합니다. 스택 맨 위에 있는 화면은 현재 표시된 view 입니다. 이 스택에 새로운 스크린을 push 하면, 디스플레이가 새 화면으로 전환됩니다. 이것이 showWelcomeScreen() 함수가 WelcomeScreen을 Navigator의 스택으로 push 하는 이유입니다. 이제, 사용자가 “Sign up” 버튼을 클릭하면, Welcome 화면이 나타납니다. 마찬가지로, Navigator에서 pop()을 호출하면 이전 화면으로 돌아갑니다. Flutter의 navigation 기능은 브라우저의 navigation 기능에 통합되어 있습니다. 이로 인하여, 브라우저의 ‘뒤로가기’ 화살표 버튼을 클릭하면 암시적으로 이전 화면으로 돌아가는 pop()이 실행 됩니다.

이렇게 완성된 프로그램을 darttutorial-41-main-step-1.dart에서 확인할 수 있습니다.

// Source: https://flutter.dev/docs/get-started/codelab-web
// darttutorial-41-main-step-1.dart

 import 'package:flutter/material.dart';
 void main() => runApp(LoginApp());
 class LoginApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
       routes: {
         '/': (context) => LoginScreen(),
         '/welcome': (context) => WelcomeScreen(),
       },
     );
   }
 }
 class LoginScreen extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       backgroundColor: Colors.grey[200],
       body: Center(
         child: SizedBox(
           width: 400,
           child: Card(
             child: LoginForm(),
           ),
         ),
       ),
     );
   }
 }
 class LoginForm extends StatefulWidget {
   @override
   _LoginFormState createState() => _LoginFormState();
 }
 class _LoginFormState extends State {
   final _firstNameTextController = TextEditingController();
   final _lastNameTextController = TextEditingController();
   final _usernameTextController = TextEditingController();
 double _formProgress = 0;
 void _showWelcomeScreen() {
     Navigator.of(context).pushNamed('/welcome');
   }
 @override
   Widget build(BuildContext context) {
     return Form(
       child: Column(
         mainAxisSize: MainAxisSize.min,
         children: [
           LinearProgressIndicator(value: _formProgress),
           Text('Sign Up',
               style: Theme.of(context)
                   .textTheme
                   .display1), // display1 changes to headline4 in 1.16
           Padding(
             padding: EdgeInsets.all(8.0),
             child: TextFormField(
               controller: _firstNameTextController,
               decoration: InputDecoration(hintText: 'First name'),
             ),
           ),
           Padding(
             padding: EdgeInsets.all(8.0),
             child: TextFormField(
               controller: _lastNameTextController,
               decoration: InputDecoration(hintText: 'Last name'),
             ),
           ),
           Padding(
             padding: EdgeInsets.all(8.0),
             child: TextFormField(
               controller: _usernameTextController,
               decoration: InputDecoration(hintText: 'Username'),
             ),
           ),
           FlatButton(
             color: Colors.blue,
             textColor: Colors.white,
             onPressed: _showWelcomeScreen,
             child: Text('Sign up'),
           ),
         ],
       ),
     );
   }
 }
 class WelcomeScreen extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       body: Center(
         child: Text('Welcome!', style: Theme.of(context).textTheme.display3),
       ),
     );
   }
 }

Step.2 Enable sign in progress tracking

앞서 [그림 4]의 53번 줄을 보면, LinearProgressIndicator가 있는 것을 볼 수 있습니다. 이는 progress bar라고도 하는데, Material 디자인에 기반해서 작업이 진행 중이라는 것을 사용자에게 보여주는 역할 입니다. 두가지 타입이 존재하는 Determinate와 Indeterminate 입니다. 전자는 0.0 ~ 1.0 사이의 값으로 현재의 진행 상황을 표시합니다. 후자는 값을 갖지 않으며(null value), 단지 진행 중이라는 의미를 전달하기 위해서 사용합니다. (참조: https://api.flutter.dev/flutter/material/LinearProgressIndicator-class.html )

이제 비활성화 되어 있는 LinearProgressIndicator를 활성화 하도록 하겠습니다. 즉, 사용자가 [그림 2]의 각각의 칸을 채워 나가면, LinearProgressIndicator의 색과 채워지는 모양이 바뀌며, 모든 칸에 대해서 사용자가 입력을 하면, 꽉찬 줄의 모양을 보여주도록 수정합니다. 이를 위해서 사용자가 각 칸 별로 글을 썼는지 확인한 후, 칸이 채워지는 만큼의 값을 LinearProgressIndicator의 파라메타로 설정한 formProgress 변수의 값을 0.0 ~ 1.0으로 변경하는 방식으로 줄의 모양이 바뀌도록 합니다. 이를 위해서 가장 먼저 할 일은 3개의 칸에 대해서 빈칸이 아니면, 각 칸별로 1/3의 값을 formProgress에 더하는 함수 입니다. 이 함수는 updateFormProgress() 이며, 다음과 같은 내용을 갑습니다. 이 함수를 LoginFormState 클래스 안에 정의합니다. 함수의 내용은 명확 합니다. 세개의 편집 가능한 칸을 담당하는 컨트롤러의 isNotEmpty Property를 통해서, 빈 칸인지 확인하고, 빈 칸이 아니라면 임시 값인 progress에 1/3의 값을 추가 합니다. 그리고 이 값을 LinearProgressIndicator가 관리하는 formProgress에 전달하여 LinearProgressIndicator의 모양이 바뀌도록 합니다. 이때, setState() Method안에서 formProgress를 설정하는데, setState()는 앞서 Flutter의 내용을 설명할 때, 화면을 업데이트 하도록 유도 한다는 부분을 다시 한번 리마인드 합니다.

void _updateFormProgress() {
   var progress = 0.0;
   var controllers = [
     _firstNameTextController,
     _lastNameTextController,
     _usernameTextController
   ];
 for (var controller in controllers) {
     if (controller.value.text.isNotEmpty) {
       progress += 1 / controllers.length;
     }
   }
 setState(() {
     _formProgress = progress;
   });
 }

다음에 할 일은 updateFormProgress() Method가 칸에 글을 입력하거나 삭제하는 활동이 발생 했을때, 자동으로 호출되도록 하는 것 입니다. 이는 LoginFormState 클래스의 build() 메소드에서 리턴되는 Form() 내의 onChanged Property 값에 updateFormProgress()를 전달하는 것으로 가능합니다. 즉, Form 안의 칸에 값이 입력/삭제 되어 변경이 발생하면, updateFormProgress() 함수가 자동 호출 되도록 하는 것 입니다. 따라서 Form()의 첫번째 줄을 다음과 같이 추가 합니다.

onChanged: () => _updateFormProgress(), 

마지막으로 칸이 모두 채워졌는지 아닌지에 대한 판단을 할 수 있게 되었으므로, 칸이 채워지면 Welcome 화면으로 넘어가지만, 빈칸이 있다면 Welcome 화면으로 넘어가지 않도록 하는 기능을 추가로 작성하도록 하겠습니다. 이는 매우 단순하게, 작업을 마치고 누르는 FlatButton의 onPressed Property를 다음과 같이 변경합니다.

onPressed: _formProgress == 1 ? _showWelcomeScreen : null, 

이는 formProgress가 1이면 showWelcomeScreen Method를 실행하고, 1이 아니면 아무런 동작을 하지 말라는 의미입니다.

이제 Step.2의 모든 작업을 마쳤으므로, “flutter run -d Chrome”으로 수정된 프로그램을 실행하고, 수정된 내용을 확인해 봅니다.

이렇게 수정한 main.dart는 darttutorial-41-main-step-2.dart와 같습니다.

// Source: https://flutter.dev/docs/get-started/codelab-web

import 'package:flutter/material.dart';

void main() => runApp(LoginApp());

class LoginApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (context) => LoginScreen(),
        '/welcome': (context) => WelcomeScreen(),
      },
    );
  }
}

class LoginScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[200],
      body: Center(
        child: SizedBox(
          width: 400,
          child: Card(
            child: LoginForm(),
          ),
        ),
      ),
    );
  }
}

class LoginForm extends StatefulWidget {
  @override
  _LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _firstNameTextController = TextEditingController();
  final _lastNameTextController = TextEditingController();
  final _usernameTextController = TextEditingController();

  double _formProgress = 0;

  void _showWelcomeScreen() {
    Navigator.of(context).pushNamed('/welcome');
  }

  void _updateFormProgress() {
    var progress = 0.0;
    var controllers = [
      _firstNameTextController,
      _lastNameTextController,
      _usernameTextController
    ];

    for (var controller in controllers) {
      if (controller.value.text.isNotEmpty) {
        progress += 1 / controllers.length;
      }
    }

    setState(() {
      _formProgress = progress;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      onChanged: () => _updateFormProgress(),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          LinearProgressIndicator(value: _formProgress),
          Text('Sign Up',
              style: Theme.of(context)
                  .textTheme
                  .display1), // display1 changes to headline4 in 1.16
          Padding(
            padding: EdgeInsets.all(8.0),
            child: TextFormField(
              controller: _firstNameTextController,
              decoration: InputDecoration(hintText: 'First name'),
            ),
          ),
          Padding(
            padding: EdgeInsets.all(8.0),
            child: TextFormField(
              controller: _lastNameTextController,
              decoration: InputDecoration(hintText: 'Last name'),
            ),
          ),
          Padding(
            padding: EdgeInsets.all(8.0),
            child: TextFormField(
              controller: _usernameTextController,
              decoration: InputDecoration(hintText: 'Username'),
            ),
          ),
          FlatButton(
            color: Colors.blue,
            textColor: Colors.white,
            onPressed:
                _formProgress == 1 ? _showWelcomeScreen : null, 
            child: Text('Sign up'),
          ),
        ],
      ),
    );
  }
}

class WelcomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text('Welcome!', style: Theme.of(context).textTheme.display3),
      ),
    );
  }
}

마무리

공식 사이트에는 추가적으로 progress indicator에 애니메이션 효과를 추가하는 예제까지 되어 있습니다. 하지만 개인적으로 애니메이션에는 큰 흥미가 없기에 이 부분은 본 글에서는 다루지 않도록 하겠습니다.

이번 글에서 어떤 부분을 느낄수 있을까요? Web 브라우저 화면을 채우고 있지만, HTML/CSS/JS에 대한 전문 지식이 그다지 필요하지 않은 것을 볼 수 있습니다. 그리고 모바일 어플리케이션을 개발하는 것과 동일하게 개발이 되는 것을 알 수 있습니다. 이렇게 플랫폼이 다르더라도 같은 기술을 사용하여 Web 어플리케이션을 개발하는 것이 가능한 매력을, Dart/Flutter 개발 환경은 제공하고 있습니다.

Creative Commons License (CC BY-NC-ND)

Dart Programmer 되기 [40]

< Web 개발 – Flutter for Web : History & Roadmap >

2019년 5월 7일, Google I/O 행사에서, Flutter 팀은 Flutter 프레임워크의 지향점을 기존 모바일에서 다양한 디바이스들로 확대하겠다는 발표를 했습니다 (참조: https://developers.googleblog.com/2019/05/Flutter-io19.html ). 이의 기술적 방법론으로, Flutter for Web, Flutter for Mobile Devices, Flutter for Desktop, Flutter for Embedded Devices를 발표 하였습니다. 이 중 첫번째 이슈가 당분간 연재하는 글의 주제이며, 두번째에서 네번째 사항은 추후 데스크탑에 대한 내용에서 다루도록 합니다.

Flutter 팀은 Flutter for Web의 초기 버전을 출시하면서, 이 기술의 목적을 명확하게 규정했습니다. 즉, Web에서 Flutter가 지향하는 초기 비전은, HTML에 최적화 된 문서 환경을 대체 할 목적이 아닙니다. 대신 정교한 UI 프레임워크의 장점을 잘 느낄 수 있는 대화식(highly interactive)의 그래픽이 풍부(graphically rich)한 콘텐츠를 제작할 수 있는 좋은 방법으로 제안하고 있습니다.

Web 용 Flutter를 소개하기 위해, Flutter 팀은 New York Times와 함께 데모 프로그램을 제작해서 공개 했습니다. New York Times는 세계적인 뉴스 보도 외에도 크로스 워드 및 기타 퍼즐 게임으로 유명합니다. 열렬한 퍼즐 매니아 들은 당시 사용하고있는 모든 기기에서 게임을 하기를 원했기 때문에, Flutter  개발 팀은 Flutter에 관심을 끌 수 있는 잠재적 인 솔루션으로 매료 되었습니다. 이에 2019년 5월 Google I/O에서 새로 업데이트 된 KENKEN 퍼즐 게임을 공개 하였습니다 (참조: https://www.nytimes.com/games/prototype/kenken#/ ). 이 게임은 Android, iOS, Web, Mac 및 Chrome OS에서 동일한 코드로 실행되도록 만들어 졌습니다.

그리고 2019년 12월에 열린 Flutter Interact 이벤트에서, Flutter 팀은 Web에 대한 Flutter의 개발 수준을 beta-level로 상향 조정한다고 발표 했습니다. 아울러 Android와 iOS에 상응하는 수준으로 Web에 대한 지원을 지속할 것 임을 발표하였습니다. 또한 향후 Desktop 어플리케이션을 위한 최적의 개발 방법으로 발전시켜 나가겠다는 발표도 함께 있었습니다.

Flutter for Web에 대한 로드맵은 GitHub의 Flutter 공식 사이트에도 명시 되어 있습니다 (참조: https://github.com/flutter/flutter/wiki/Roadmap#2019 ). 이곳에 명시된, 2020년의 목표는 “flutter create; flutter run”을 통해서 Flutter로 개발한 어플리케이션이 Web 브라우저, macOS, Windows, Android, Fuchsia 및 iOS에서 실행하도록 하는 것 입니다. 아울러 How-Reload, 플러그인, 테스트 및 릴리스 모드 빌드를 일관되게 지원하게 한다고 합니다. 또한, Material 디자인 위젯 라이브러리가, 이 모든 플랫폼에서 잘 작동 하도록 하는 것을 목표로 한다고 합니다.

참고로 GitHub의 Flutter 공식 페이지의 WiKi를 방문하면, Flutter에 대한 각종 기술적 자료와 함께, Flutter for Web과 같이 시험적으로 만들어 지고 기능(experimental features)을 확인할 수 있으니, 참조하기 바랍니다. 여기에는 기존 iOS/Android App을 Flutter로 변환하기, dart:ffi를 통한 native 코딩, macOS Desktop 어플리케이션을 Flutter로 만드는 법 등 다양한 시도들이 이루어 지고 있는 것을 볼 수 있습니다.

마무리

Flutter for Web은 살펴본 것처럼 공식적으로 알려지기 시작한 기간이 매우 짧은 기술 입니다. 하지만, 빠른 속도로 Web 개발자들의 관심을 모으고 있습니다. 특히 2020년 올해는 기술적인 도약이 매우 기대되는 한해가 될 것으로 보이므로, 공식 홈페이지를 주기적으로 살펴보면서, 어떤 변화가 있을지 꾸준하게 파악하는 것이 필요해 보입니다.

Creative Commons License (CC BY-NC-ND)