Dart/Flutter 入門

+ 最強の勤怠アプリを作った話

2017/2/27

2018/7/7 一部更新

自己紹介

  • 西田雅博
  • github.com/adwd
  • 株式会社ビズリーチ HRMOS事業部
  • 人事戦略クラウドサービス HRMOS 作ってます
  • Scala/React書いてます

今日の内容

  • Dart
  • Flutter
  • Flutterを作って最強の勤怠アプリを作った話
    • おまけです 😂

資料を見てる人向け: スライドを↓に進めるのを多用してます

Dart

Dartってどんな言語?

  • 2011年にGoogleが発表
  • Java、JSに似たシンタックスの動的型付け言語
    • JITコンパイルされ、DartVM上で動作する
    • dart2jsでJSに変換できるのでブラウザでも動く
  • ECMA-408で標準化されている

触ってみた感触としては

  • Java,JS等がかければすんなり書ける
  • 型を書いても書かなくてもいい 😳
  • async/await, class周りのサポート
  • パッケージマネージャー、IDEサポートの充実
  • "ちょうどいい"言語
    • Javaのレガシーさを取り除いている
    • Goほど記述量は多くない
    • Scalaほど難しくない

歴史

  • 当初はブラウザ上で動作し、JavaScriptを置き換える目的
    • 各ブラウザがDartVMを搭載する必要がある 😨
    • やっぱりそれは無理なのであきらめた
    • (dart2jsでJSに変換して動かすことはできる)

それから時は流れ…

2016年、Dart復権の兆し?

  • 2016年にGoogle社内で一番書かれたプログラミング言語はDart
  • 書きやすさと動作速度からRubyに代わってSassの実装に採用される
  • 大きな収益を持つGoogle AdSense/AdWordsのフロントエンドがAngular Dartで書き換えられる

少なくともGoogleは本気っぽい?

これからのDart

  • 現在活発な主なプロジェクト
    • Angular Dart
    • DDC(Dart Dev Compiler)
    • Flutter
    • Fuchsia

あくまで「現時点の情報では」です!

Angular Dart

  • Angular2からそれまでJS/TSと共通だったコードベースを分離
  • 専任のAngularDartチームができた
  • よりDartの機能を活かし、自然な構文、高速に

DDC(Dart Dev Compiler)

  • 静的チェックを行い可読性の高いES2015を吐くコンパイラ
    • dart2jsが吐くjsを人が読むのはキツい、サイズも大きい
    • 静的チェックの情報をjsへのコンパイルに使う
  • Strong mode
    • Dartの言語標準より厳しいチェックで実行時エラーを減らす

Flutter

  • Dartでモバイルアプリを書くフレームワーク
  • コンセプト
    • 高い生産性
    • ハイパフォーマンス
    • クロスプラットフォーム(Android/iOS)
  • 詳しくは後述

Fuchsia

  • Googleが開発中のオープンソースOS
  • LinuxカーネルにかわりMagentaを採用
  • 組み込み〜スマートフォン〜PCがターゲット
  • 主なプログラミング言語はDart、UIはFlutter

Dartの未来は
明るい!!

Dartをはじめる

Dartのインストール

Mac


							$ brew tap dart-lang/dart
							$ brew install dart
						

Windows


							C:\> choco install dart-sdk
						
Linuxは省略

Dart Pad

ブラウザからDartを動かす

https://dartpad.dartlang.org/

Dart Padでコンソールアプリ

WebアプリもOK

IntelliJ plugin

  • シンタックスハイライト
  • デバッグ実行
  • などなど
  • Dartプロジェクトの新規作成
    • コンソールアプリケーション
    • Webアプリケーション
    • Angularアプリケーション
    • Flutterアプリケーション

Dartで何かを作り始めたいとき便利!

Dart Padですぐ始められる!

IntelliJ pluginもある!

あなたとDart

いますぐダウンロー

ド 💪

Dartのシンタックス

Javaっぽいシンタックス

Optional Types

DOM API

Cascade notation

async/await

Stream

ほかにもいろいろ

  • final, const
  • optional, named, default function parameters
  • named, factory constructor
  • class, mixin
  • generics
  • など
https://www.dartlang.org/guides/language/language-tour

馴染みのある構文

必要十分な機能

今から書ける、それがDart 💪

Flutter

Flutterのコンセプト

  • 高い生産性
  • ハイパフォーマンス
  • クロスプラットフォーム(Android/iOS)

高い生産性

  • Dart言語の生産性
  • 関数型リアクティブフレームワーク
  • Material DesignのUIライブラリ

ハイパフォーマンス

  • モバイルの2D表示に特化して最適化されたレンダリングエンジン
  • WebViewなどを使用せずすべてネイティブ
    • FlutterエンジンのC++コードは(Android: Android NDK, iOS: LLVM)でコンパイル
    • DartのコードはDartコンパイラでネイティブコードにAOTコンパイル

クロスプラットフォーム(Android/iOS)

触った印象

関数型でリアクティブ

  • React/Fluxにインスパイアされたコンポーネント指向、状態管理、単方向データフロー
    • コンポーネントの木構造を宣言的に記述
    • StatlessWidget, StatefulWidget, setState
    • React/Fluxやってれば大体そういう感じ
    • Flutter System Architecture

理想的なフレームワーク

Flutterをはじめる

Flutterをインストール

  • Mac, Windows, Linuxにインストールできる

Flutterをインストール


							$ cd ~/development
							$ unzip ~/Downloads/flutter_macos_v0.5.1-beta.zip
							$ export PATH=`pwd`/flutter/bin:$PATH

							# 必要なものが入っているかチェックしてくれる
							$ flutter doctor

							# XCodeでiOSシミュレーターとかAndroid Studioなどをインストール
						

flutter create


							$ flutter create my_app 
							Creating project my_app...

							...

							All done! In order to run your application, type:

								$ cd my_app
								$ flutter run

							Your main program file is lib/main.dart in the my_app directory.

						

flutter run


							$ cd my_app
							$ open -a Simulator # iOSシミュレータを起動
							$ flutter run
						
flutter app screen shot

実機へのインストールも簡単 😍


							$ flutter build apk # flutter build ios でiOSビルド(MacOSのみ)
							$ flutter install
						

小さなアプリでビルド20秒、インストール10秒 🚀

iOSアプリをビルド・インストールするにはApple Developerアカウントの登録などが必要

IntelliJのplugin

  • Flutterプロジェクトの新規作成
  • Cmd+d でデバッグ実行
  • Cmd+Alt+; でホットリロード
  • Shift+Cmd+Alt+; でフルリロード

webpackに慣れすぎて↑が無設定で動くことに感動 😭

公式のexamplesがすごい

便利なCLIが用意されている!

IDEの連携もあり開発・デバッグが非常に楽!

Flutter 入門

プロジェクト構成


							$ flutter create my_app
							my_app
							├── android
							├── ios
							├── lib # libの中にコードを書いていく
							│   └── main.dart # アプリケーションのエントリーポイント
							└── pubspec.yaml # package.jsonとかpom.xmlみたいなの
						

Flutter アプリ最小構成


							// lib/main.dart
							import 'package:flutter/material.dart';

							void main() {
								runApp(
									new Center(
										child: new Text('Hello, Flutter!')
									)
								);
							}
						
  • エントリーポイントはlib/main.dartのmain関数
  • runApp関数にWidgetのインスタンスを渡す
  • Widgetは子(child/children)を持ち、Widgetの木構造を宣言的に記述する

Widget

  • ReactでいうComponent
  • コンポーネント化されたUI部品
  • StatelessWidget, StatefulWidgetがある
  • 親から子へデータを渡す
    • 子Widgetのコンストラクタに引数として
    • Reactならprops
  • 子から親はコールバック関数

更新と差分反映

  • Flutterはユーザーの操作などでデータの変更があった場合、関係するWidgetのみbuildを再実行する
  • Reactと同じで富豪的、宣言的にUIを書きつつ動作速度を落とさない

StatelessWidget

  • StatelessWidgetを継承したクラスにbuildメソッドをoverrideする
  • React.Componentと同じですね 😇

							class HelloWorld extends StatelessWidget {
								// コンストラクタ
								// {}の中は名前付き引数 new HelloWorld(name: 'foo');
								HelloWorld({Key key, this.name}): super(key: key);
								final String name;

								// buildメソッドをoverrideする
								@override
								Widget build(BuildContext context) {
									return new Text('Hello, $name!');
								}
							}
						

StatefulWidget

  • 状態を持つWidgetを作ることができる
  • StatefulWidgetクラスとState<T>クラスをつかう

間違っとったらスマン!! 😳

マサカリポイント!!! 🔨

StatefulWidget / State<T>


							class Counter extends StatefulWidget {
								...

								@override
								_CounterState createState() => new _CounterState();
							}
							
							class _CounterState extends State<Counter> {
								...

								@override
								Widget build(BuildContext context) {
									return new ...;
								}
							}
						

StatefulWidget / State<T>

  • StatefulWidget
    • State<T>のインスタンスを持つ
  • State<T>
    • TはStatefulWidgetを継承したクラス
    • 状態を内部で管理する
    • buildメソッドを持つ

StatefulWidgetとState<T>の生存期間が違うとかなんとか

サンプル

Counter Widget

  • 親WidgetからTitleを受け取る
  • 状態としてカウンターを持つ
  • ボタンを押したらカウンターを1増やす

StatefulWidget

  • StatefulWidgetを継承したクラスにcreateStateメソッドをoverrideする
    • createStateの戻り値の型はState<T>を継承したクラス

							class Counter extends StatefulWidget {
								Counter({Key key, this.title}) : super(key: key);

								final String title;

								@override
								_CounterState createState() => new _CounterState();
							}
						

StatefulWidget

  • コンストラクタの引数からデータを受け取ることができる
  • そのデータはStateクラスからも取得できるが、Stateの中からは更新しない
  • ReactでいうPropsやね!

							class Counter extends StatefulWidget {
								Counter({Key key, this.title}) : super(key: key);

								final String title;

								@override
								_CounterState createState() => new _CounterState();
							}
						

State<T>

  • StatefulWidgetを継承したクラスを型パラメータに取る
  • buildメソッドをoverrideする

							class Counter extends StatefulWidget {
								...

								@override
								_CounterState createState() => new _CounterState();
							}

							class _CounterState extends State<Counter> {
								...

								@override
								Widget build(BuildContext context) {
									return new ...;
								}
							}
						

State<T>

  • this.configが<T>の型になりアクセスできる

							class Counter extends StatefulWidget {
								...
								final String title;
								...
							}

							class _CounterState extends State<Counter> {
								...

								@override
								Widget build(BuildContext context) {
									return new Text(this.config.title); // I am Counter
								}
							}
						

State<T>

  • 状態を変更するにはsetStateメソッドの中で自身のメンバ変数を変更する

							class _CounterState extends State<Counter> {
  							int _counter = 0;

								void _incrementCounter() {
									setState(() => _counter++);
								}

								@override
								Widget build(BuildContext context) {
									return new ...
										new FloatingActionButton(onPressed: _incrementCounter, ...)
									);
								}
							}
						

コード全体


							import 'package:flutter/material.dart';

							void main() {
								runApp(new MaterialApp(
									home: new Counter(title: 'I am Counter'),
								));
							}

							class Counter extends StatefulWidget {
								Counter({Key key, this.title}) : super(key: key);

								final String title;

								@override
								_CounterState createState() => new _CounterState();
							}

							class _CounterState extends State<Counter> {
								int _counter = 0;

								void _incrementCounter() {
									setState(() => _counter++);
								}

								@override
								Widget build(BuildContext context) {
									return new Scaffold(
										body: new Center(
											child: new Column(
												children: [
													new Text(this.config.title),
													new Text('Button tapped $_counter time(s).')
												],
												mainAxisAlignment: MainAxisAlignment.center
											)
										),
										floatingActionButton: new FloatingActionButton(
											onPressed: _incrementCounter,
											tooltip: 'Increment',
											child: new Icon(Icons.add),
										)
									);
								}
							}
						
Reactとの比較
React Flutter
Component#props StatefulWidgetのメンバ変数、State<T>のthis.config
Component#state State<T>のメンバ変数
Component#setState State<T>#setState
ライフサイクルメソッド
React Component Flutter State<T>
componentDidMount
componentWillReceiveProps
componentWillUnmount
など
initState
didUpdateConfig
dispose
など

React/Fluxインスパイア

Rectやってれば移行は容易そう

ReactNativeでよいのでは 😇

最強の勤怠アプリを作った話

ここからはおまけみたいなもんです 😂

現在HRMOS勤怠管理というプロダクトを作っています

しかしそのアプリには問題があった...

※ 個人的な意見・感想です

出勤・退勤画面を御覧ください

そっけない

※ 個人的な感想・意見です

  • みんないろいろな思いを抱えて出勤・退勤しているはず
    • 「めちゃ疲れてるけど今日も仕事頑張るぞ…!」
    • 「今日も俺よく働いた!えらい!」
    • 「3時までゲームしてたから超眠いけどなんとか出勤できた」

開発当初同様の指摘はあった!

たしかに

開発リーダーによる謎のスルー

  • 開発リーダーによる「 たしかに」という回答
  • しかしチケットに起票されるわけでもない

※ 実際は謎でも何でもない適切な判断が行われています

だったら自分で作るしかない!! 💪 💪

みんなの勤怠に込めた思いを受け止める

そんな勤怠アプリをFlutterで作る!

設計

  • 自分用、雑に作る
    • 出勤・退勤以外の機能はいらない
      • 本家は有給申請とかいろいろできる!
    • アプリにログイン情報埋め込んで起動時に毎回ログイン
    • ログイン、出勤、退勤のHTTPリクエストをHRMOSサーバーに送る
      • APIを公開してるわけではなく開発者だから知ってる内部的なAPIを叩いてます

HTTPのやり取りする処理を作る


							import 'package:flutter/http.dart' as http;

							Future<bool> signin() async {
								var client = new http.Client();

								var headers = new Map()
									..['Content-Type'] = 'application/json';

								var body = new Map()
									..['secret1'] = 'secretなデータが'
									..['secret2'] = 'いろいろありますね';
								
								var response = await client.post(
									'https://example.com/',
									headers: headers,
									body: JSON.encode(body)
								);

								// responseをよしなに処理
							}
						

ログイン・出勤・退勤処理はできた

Futureが返ってくる普通の使いやすいhttpクライアントがあった

Httpクライントの注意点

Universalなhttpクライアントがない

どこでも動くDartだから、httpクライアントも当然どこでも動くと思いハマった 😂

プラットフォームごとのhttpクライアント

dart:io
サーバーサイドDartVM向け
package:http
サーバーサイド向けClientとブラウザ上向けBrowserClient
package:flutter/http.dart
flutter向け

dart-lang/httpにIssueがあり、コメントによると4月には解決する?

アニメーション

出勤・退勤時にアニメーションしてユーザーの気持ちを受け止めたい

アニメーションを実装するには

Animation<T>とAnimationControllerを使う

Animation<T>

  • 変化する値(型はT)を持つクラス
  • その値をアニメーションに使う
    • 例: Animation<double>の値をWidgetの表示位置に使ってアニメーションする

							new Positioned(
								top: animation.value * 200.0, // 0から200の間の値を変化
								child: new Text('上から下に動くぞ!')
							);
						

AnimationController

  • Animationの値の変化を管理する
    • 例: ボタンのonPressイベントでAnimationController#stopメソッドを実行してAnimationの値の変化を停止

							new FloatingActionButton(
								child: new Text('押すと止まる'),
								onPressed: () => _animationController.stop(),
							);
						

だいたいこうなる


							// 2秒間にAnimationの値を初期値から終了値まで変化させる
							// AnimationControllerをつくり、forwardメソッドで開始する
							_controller = new AnimationController(
								duration: const Duration(seconds: 2),
								vsync: this,
							)..forward();

							// fastOutSlowInな値の変化を0.0から1.0まで持つAnimation
							_animation = new CurvedAnimation(
								parent: _controller,
								curve: new Interval(0.0, 1.0, curve: Curves.fastOutSlowIn)
							);

							// 子コンポーネントの不透明度を設定できるOpacity Widgetに
							// Textを入れて、不透明度の値opacityをAnimationクラスから取得する
							new Opacity(
								child: new Text('透明から徐々に見えるようになるぞ!'),
								opacity: _animation.value
							)
						

アニメーションできた

exampleにアニメーションのサンプルがあった 😂

公式ドキュメントだけじゃ実装できなかった

そして最強の勤怠アプリが完成した

出勤

出勤のしがいがある!

退勤

一日の疲れをねぎらってくれる!

ユーザーの気持ちに寄り添う最強の勤怠アプリが完成した

いろいろすいません

アプリ開発にはいろいろなセンスが必要という学びを得た 😇

まとめ

Dart

  • DDCなどまだこれからな部分がある
  • Googleが本気なのかどうなのか!? 😭
  • 言語自体は学習コストが低く必要十分な機能で書きやすい

Flutter

  • React/Fluxインスパイアで書きやすい
  • iOS/Androidのアプリが一つのコードでカバーできて強い
  • Material Designなアプリならすぐに作れる

Dart, Flutterともにとても良いと思います

Googleさんオナシャス 😭

おわり