2019年6月25日(火)開催のアシアル技術セミナーでの発表資料
アプリ開発の新たな選択肢が登場!Google社製Flutterのご紹介HIROAKI TSUTSUMI堤 啓彰 アシアル株式会社
View Slide
堤 啓彰 Tsutsumi Hiroakiエンジニア(主にフロントエンド)業務で使ったことのある⾔語:JavaScript PHP C#多少は書ける⾔語:Java Swiftここ1年ほどは、Cordovaでのアプリ開発※ Flutterを業務で使った経験はなし
FlutterとはCordova/Flutterの⽐較DartとはFlutterの基礎まとめTODAY’s AGENDA
FlutterとはGoogle製の、Android/iOS向けのモバイルアプリを開発できるクロスプラットフォーム開発ツール2018年12⽉4⽇、バージョン1.0が正式リリース- 現時点での最新版はバージョン1.5.4Githubでのスター数は67,000超え
Flutterとは > Githubスター数の推移2018年2⽉27⽇、β版公開
Flutterとは > モバイルアプリだけではないFlutterFlutter for webFlutterのコードからブラウザで実⾏可能なWebアプリケーションを⽣成する技術- 現在テクニカルプレビュー
Flutterとは > モバイルアプリだけではないFlutterDesktop Embedding for FlutterFlutterのコードからデスクトップアプリケーション(Windows/MacOS/Linux)を⽣成する技術- 現在開発中
Flutterとは > モバイルアプリだけではないFlutterFlutterを習得すれば、Android/iOSに加えて、Web/デスクトップアプリケーションも作れるように
Cordova/Flutterの⽐較Cordova Flutter開発⾔語 HTML/CSS/JavaScript Dartレンダリング WebView ネイティブホットリロード 不可 可
Cordova/Flutterの⽐較 > レンダリング:CordovaOS(Android/iOS)WebViewUIUIはWebViewによりレンダリングネイティブアプリよりも、低パフォーマンス
Cordova/Flutterの⽐較 > レンダリング:FlutterOS(Android/iOS)UIUIはネイティブでレンダリングネイティブアプリと遜⾊ないパフォーマンス
Cordova/Flutterの⽐較 > ホットリロードホットリロードソースコードの変更を、アプリを再ビルドせず、状態を維持したまま反映- 修正が(基本的に)1秒未満で反映- フルリロードしても10秒程度Cordovaでは現状できない(ブラウザでは可能)
Cordova/Flutterの⽐較 > ホットリロードDemo
Cordova/Flutterの⽐較Flutterを使えば、ハイパフォーマンスなアプリを、ホットリロードを⽤いて⾼速に開発可能
Dartとは > Dartの歴史Googleによりリリース- JavaScriptを代替することが⽬的- ブラウザへのDartVMの搭載が⽬標2011
Dartとは > Dartの歴史Googleによりリリース- JavaScriptを代替することが⽬的- ブラウザへのDartVMの搭載が⽬標ブラウザへのDartVMの搭載を断念20112015
Dartとは > Dartの歴史Googleによりリリース- JavaScriptを代替することが⽬的- ブラウザへのDartVMの搭載が⽬標ブラウザへのDartVMの搭載を断念Googleのフロントエンド開発の標準⾔語としてTypeScriptが採⽤201120152017
Dartとは > Dartの歴史Googleによりリリース- JavaScriptを代替することが⽬的- ブラウザへのDartVMの搭載が⽬標20112015ブラウザへのDartVMの搭載を断念2017Googleのフロントエンド開発の標準⾔語としてTypeScriptが採⽤現在Flutterの普及とともに、再び注⽬を集める
Dartとは > Dartの特徴Dartの特徴• オブジェクト指向• 静的型付け
Dartとは > サンプルコードclass Person {String _firstName;String _lastName;Person(this._firstName, this._lastName);String getFullName() {return '$_lastName $_firstName';}}void main() {Person person = Person('Hiroaki', 'Tsutsumi');print(person.getFullName());}
Dartとは > Dartを使ってみた所感所感• くせがなく、書きやすい• オブジェクト指向を理解していれば、習得は⽤意• Javaによく似ており、Javaを触ったことがあればすぐ馴染める
Dartとは > カスケード記法カスケード記法• 同⼀オブジェクトに対する操作を、戻り値がなくても連続して記述できる記法• 「..」(ドット2つ)で操作を連結して記述
Dartとは > カスケード記法var button = querySelector('#confirm');button.text = 'Confirm';button.classes.add('important');button.onClick.listen((e) => window.alert('Confirmed!'));カスケード記法なし
Dartとは > カスケード記法var button = querySelector('#confirm');button.text = 'Confirm';button.classes.add('important');button.onClick.listen((e) => window.alert('Confirmed!'));カスケード記法なしquerySelector('#confirm')..text = 'Confirm'..classes.add('important')..onClick.listen((e) => window.alert('Confirmed!'));カスケード記法あり
Dartとは > なぜDartが採⽤されたのかDartはコンパイル⽅式として、JIT(Just In Time)とAOT(Ahead Of Time)の両⽅をサポート- JIT:コンパイル速・パフォーマンス低- AOT:コンパイル遅・パフォーマンス⾼
Dartとは > なぜDartが採⽤されたのかDartはコンパイル⽅式として、JIT(Just In Time)とAOT(Ahead Of Time)の両⽅をサポート- JIT:コンパイル速・パフォーマンス低- AOT:コンパイル遅・パフォーマンス⾼開発時はJITによる開発サイクルの⾼速化プロダクションではAOTによるパフォーマンス向上その他にもいくつかの理由があり、詳細はhttps://flutter.dev/docs/resources/faq#why-did-flutter-choose-to-use-dart(英語)を参照
Flutterの基礎すべてがWidget
Flutterの基礎 > すべてがWidgetFlutterアプリのUIは、Widgetの組み合わせWidgetの中に⼊れ⼦でWidgetを追加して、Widget Treeを構成していくことでUIを構築
Flutterの基礎 > すべてがWidget@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('TODOリスト')),body: Column(children: [...],),);}
@overrideWidget build(BuildContext context) {return Scaffold(...,body: Column(children: [Row(children: [...],),Expanded(child: ListView.builder(...),),],),);}Flutterの基礎 > すべてがWidget
@overrideWidget build(BuildContext context) {...,body: Column(children: [Row(children: [Flexible(child: TextField(...),),Padding(padding:EdgeInsets.only(right: 10.0)),RaisedButton(...),],),...,Flutterの基礎 > すべてがWidget
Flutterの基礎 > 豊富なWidgetFlutterは標準で⼤量のWidgetを提供https://flutter.dev/docs/development/ui/widgets
Flutterの基礎 > 豊富なWidget:マテリアルデザインhttps://flutter.dev/docs/development/ui/widgets/material
Flutterの基礎 > 豊富なWidget:Cupertinohttps://flutter.dev/docs/development/ui/widgets/cupertino
Flutterの基礎 > 豊富なWidgetどのようなWidgetがあり、そのWidgetで何ができるのかを把握することが重要
Flutterの基礎StatelessとStateful
Flutterの基礎 > StatelessとStatefulStatelessは状態を持たないWidgetStatefulは状態を持つWidget外部との通信やユーザの操作により状態が変化しないWidgetはStatelessに状態が変化するWidgetはStatefulに
Flutterの基礎 > Statelessの例各タスクはStateless• 渡された⽂字列を表⽰しているだけ• ユーザの操作により、保持している情報は変化しないため
Flutterの基礎 > StatelessWidgetclass TodoListItem extends StatelessWidget {final int _index;final String _todo;final Function _removeTodo;TodoListItem(this._index, this._todo, this._removeTodo);@overrideWidget build(BuildContext context) {return Card(child: ListTile(title: Text(_todo),trailing: IconButton(icon: Icon(Icons.remove_circle, color: Colors.red),onPressed: () => _removeTodo(_index),),...,}
Flutterの基礎 > StatelessWidgetclass TodoListItem extends StatelessWidget {final int _index;final String _todo;final Function _removeTodo;TodoListItem(this._index, this._todo, this._removeTodo);@overrideWidget build(BuildContext context) {return Card(child: ListTile(title: Text(_todo),trailing: IconButton(icon: Icon(Icons.remove_circle, color: Colors.red),onPressed: () => _removeTodo(_index),),...,}StatelessWidgetを継承
Flutterの基礎 > StatelessWidgetclass TodoListItem extends StatelessWidget {final int _index;final String _todo;final Function _removeTodo;TodoListItem(this._index, this._todo, this._removeTodo);@overrideWidget build(BuildContext context) {return Card(child: ListTile(title: Text(_todo),trailing: IconButton(icon: Icon(Icons.remove_circle, color: Colors.red),onPressed: () => _removeTodo(_index),),...,}buildメソッドをoverride
Flutterの基礎 > 状態変化の例
Flutterの基礎 > StatefulWidgetfinal _todos = [];...@overrideWidget build(BuildContext context) {return Scaffold(...,RaisedButton(...,onPressed: () => setState(() => _todos.add(_todo)),),],),...child: ListView.builder(itemCount: _todos.length,itemBuilder: (BuildContext context, int index) =>TodoListItem(index, _todos[index], _removeTodo),), ...
Flutterの基礎 > StatefulWidgetfinal _todos = [];...@overrideWidget build(BuildContext context) {return Scaffold(...,RaisedButton(...,onPressed: () => setState(() => _todos.add(_todo)),),],),...child: ListView.builder(itemCount: _todos.length,itemBuilder: (BuildContext context, int index) =>TodoListItem(index, _todos[index], _removeTodo),), ...State(状態)を定義
Flutterの基礎 > StatefulWidgetfinal _todos = [];...@overrideWidget build(BuildContext context) {return Scaffold(...,RaisedButton(...,onPressed: () => setState(() => _todos.add(_todo)),),],),...child: ListView.builder(itemCount: _todos.length,itemBuilder: (BuildContext context, int index) =>TodoListItem(index, _todos[index], _removeTodo),), ...Stateを更新(タスクを追加)
Flutterの基礎 > StatefulWidgetfinal _todos = [];...@overrideWidget build(BuildContext context) {return Scaffold(...,RaisedButton(...,onPressed: () => setState(() => _todos.add(_todo)),),],),...child: ListView.builder(itemCount: _todos.length,itemBuilder: (BuildContext context, int index) =>TodoListItem(index, _todos[index], _removeTodo),), ...Stateの更新を検知し、buildが呼ばれる
Flutterの基礎 > StatefulWidgetfinal _todos = [];...@overrideWidget build(BuildContext context) {return Scaffold(...,RaisedButton(...,onPressed: () => setState(() => _todos.add(_todo)),),],),...child: ListView.builder(itemCount: _todos.length,itemBuilder: (BuildContext context, int index) =>TodoListItem(index, _todos[index], _removeTodo),), ...State(タスク⼀覧)が変化しているため、ListViewが更新される
Flutterの基礎Flutterでのアニメーション
Flutterの基礎 > FlutterでのアニメーションFlutterでは、アニメーションもWidgetアニメーションに関しても、豊富なWidgetを提供もちろん、⾃作も可能
Flutterの基礎 > アニメーション例:Opacity
Flutterの基礎 > アニメーション例:Opacitybool _visible = true;@overrideWidget build(BuildContext context) {...body: Center(child: AnimatedOpacity(opacity: _visible ? 1.0 : 0.0,duration: Duration(seconds: 1),child: Container(width: 200.0, height: 200.0, color:Colors.blue),),),floatingActionButton: FloatingActionButton(child: Icon(Icons.flip),onPressed: () => setState(() => _visible = !_visible),),
Flutterの基礎 > アニメーション例:Opacitybool _visible = true;@overrideWidget build(BuildContext context) {...body: Center(child: AnimatedOpacity(opacity: _visible ? 1.0 : 0.0,duration: Duration(seconds: 1),child: Container(width: 200.0, height: 200.0, color:Colors.blue),),),floatingActionButton: FloatingActionButton(child: Icon(Icons.flip),onPressed: () => setState(() => _visible = !_visible),),Stateを定義
Flutterの基礎 > アニメーション例:Opacitybool _visible = true;@overrideWidget build(BuildContext context) {...body: Center(child: AnimatedOpacity(opacity: _visible ? 1.0 : 0.0,duration: Duration(seconds: 1),child: Container(width: 200.0, height: 200.0, color:Colors.blue),),),floatingActionButton: FloatingActionButton(child: Icon(Icons.flip),onPressed: () => setState(() => _visible = !_visible),),Stateを更新
Flutterの基礎 > アニメーション例:Opacitybool _visible = true;@overrideWidget build(BuildContext context) {...body: Center(child: AnimatedOpacity(opacity: _visible ? 1.0 : 0.0,duration: Duration(seconds: 1),child: Container(width: 200.0, height: 200.0, color:Colors.blue),),),floatingActionButton: FloatingActionButton(child: Icon(Icons.flip),onPressed: () => setState(() => _visible = !_visible),),Stateの変更を検知し、buildが呼ばれる
Flutterの基礎 > アニメーション例:Opacitybool _visible = true;@overrideWidget build(BuildContext context) {...body: Center(child: AnimatedOpacity(opacity: _visible ? 1.0 : 0.0,duration: Duration(seconds: 1),child: Container(width: 200.0, height: 200.0, color:Colors.blue),),),floatingActionButton: FloatingActionButton(child: Icon(Icons.flip),onPressed: () => setState(() => _visible = !_visible),),Opacityの変更に応じて、アニメーション
Flutterの基礎 > アニメーション例:Container
Flutterの基礎 > アニメーション例:Containerdouble _width = 100;double _height = 100;Color _color = Colors.blue;@overrideWidget build(BuildContext context) {...child: AnimatedContainer(width: _width,height: _height,decoration: BoxDecoration(color: _color),duration: Duration(seconds: 1),curve: Curves.easeInOut,),...onPressed: () {setState(() {final random = Random();_width = random.nextInt(300).toDouble();_height = random.nextInt(300).toDouble();_color = Color.fromRGBO(random.nextInt(256), random.nextInt(256),random.nextInt(256), 1);});
Flutterの基礎 > アニメーション例:Containerdouble _width = 100;double _height = 100;Color _color = Colors.blue;@overrideWidget build(BuildContext context) {...child: AnimatedContainer(width: _width,height: _height,decoration: BoxDecoration(color: _color),duration: Duration(seconds: 1),curve: Curves.easeInOut,),...onPressed: () {setState(() {final random = Random();_width = random.nextInt(300).toDouble();_height = random.nextInt(300).toDouble();_color = Color.fromRGBO(random.nextInt(256), random.nextInt(256),random.nextInt(256), 1);});Stateを定義
Flutterの基礎 > アニメーション例:Containerdouble _width = 100;double _height = 100;Color _color = Colors.blue;@overrideWidget build(BuildContext context) {...child: AnimatedContainer(width: _width,height: _height,decoration: BoxDecoration(color: _color),duration: Duration(seconds: 1),curve: Curves.easeInOut,),...onPressed: () {setState(() {final random = Random();_width = random.nextInt(300).toDouble();_height = random.nextInt(300).toDouble();_color = Color.fromRGBO(random.nextInt(256), random.nextInt(256),random.nextInt(256), 1);});Stateを更新
Flutterの基礎 > アニメーション例:Containerdouble _width = 100;double _height = 100;Color _color = Colors.blue;@overrideWidget build(BuildContext context) {...child: AnimatedContainer(width: _width,height: _height,decoration: BoxDecoration(color: _color),duration: Duration(seconds: 1),curve: Curves.easeInOut,),...onPressed: () {setState(() {final random = Random();_width = random.nextInt(300).toDouble();_height = random.nextInt(300).toDouble();_color = Color.fromRGBO(random.nextInt(256), random.nextInt(256),random.nextInt(256), 1);});Stateの変更を検知し、buildが呼ばれる
Flutterの基礎 > アニメーション例:Containerdouble _width = 100;double _height = 100;Color _color = Colors.blue;@overrideWidget build(BuildContext context) {...child: AnimatedContainer(width: _width,height: _height,decoration: BoxDecoration(color: _color),duration: Duration(seconds: 1),curve: Curves.easeInOut,),...onPressed: () {setState(() {final random = Random();_width = random.nextInt(300).toDouble();_height = random.nextInt(300).toDouble();_color = Color.fromRGBO(random.nextInt(256), random.nextInt(256),random.nextInt(256), 1);});各Stateの変更に応じて、アニメーション
まとめメリット・デメリットl 学習コストは低くはないl Dart⾃体は馴染みやすいl Widgetを使いこなすのが⼤変l ⽇本語の情報はまだ少ないl ネイティブアプリに近いパフォーマンスl ホットリロードによる⾼速な開発サイクル