Slide 1

Slide 1 text

雰囲気でFlutterやってみた Flutter Meetup Tokyo #1 2018/04/18 (Wed) @syarihu

Slide 2

Slide 2 text

Taichi Sato (@syarihu) ● Money Forward ○ Android Engineer ● TechBooster

Slide 3

Slide 3 text

Taichi Sato (@syarihu) ● Android開発歴 7年くらい

Slide 4

Slide 4 text

Taichi Sato (@syarihu) ● Android開発歴 7年くらい ● Flutter歴

Slide 5

Slide 5 text

Taichi Sato (@syarihu) ● Android開発歴 7年くらい ● Flutter歴 3日くらい

Slide 6

Slide 6 text

Flutter 気になるけど よくわからん…

Slide 7

Slide 7 text

そうだ、雰囲気でやってみよう

Slide 8

Slide 8 text

昔つくった個人アプリ(の一部)を Flutterで作ってみた

Slide 9

Slide 9 text

愛工大シャトルバス時刻表

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

これを Flutterで 作ってみる

Slide 16

Slide 16 text

やること ● Widgetに分解 ● Widgetを配置 ● APIから取得した結果を Widgetに反映

Slide 17

Slide 17 text

1. Widgetに分解

Slide 18

Slide 18 text

Toolbar

Slide 19

Slide 19 text

AppBar Toolbar

Slide 20

Slide 20 text

ViewPager

Slide 21

Slide 21 text

ViewPager TabBarView

Slide 22

Slide 22 text

ListView

Slide 23

Slide 23 text

ListView ListView

Slide 24

Slide 24 text

Bottom NavigationView

Slide 25

Slide 25 text

Bottom NavigationView Bottom NavigationView

Slide 26

Slide 26 text

2. Widgetを配置

Slide 27

Slide 27 text

AppBar

Slide 28

Slide 28 text

AppBar void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: '愛工大シャトルバス時刻表', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: '愛工大シャトルバス時刻表 '), ); } }

Slide 29

Slide 29 text

AppBar void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: '愛工大シャトルバス時刻表', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: '愛工大シャトルバス時刻表 '), ); } }

Slide 30

Slide 30 text

AppBar void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: '愛工大シャトルバス時刻表', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: '愛工大シャトルバス時刻表 '), ); } }

Slide 31

Slide 31 text

AppBar void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: '愛工大シャトルバス時刻表', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: '愛工大シャトルバス時刻表 '), ); } }

Slide 32

Slide 32 text

AppBar

Slide 33

Slide 33 text

AppBar class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); }

Slide 34

Slide 34 text

AppBar class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); }

Slide 35

Slide 35 text

AppBar class _MyHomePageState extends State { @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center(), ); } }

Slide 36

Slide 36 text

AppBar

Slide 37

Slide 37 text

TabBarView

Slide 38

Slide 38 text

TabBarView class _MyHomePageState extends State { ... @override Widget build(BuildContext context) {

Slide 39

Slide 39 text

TabBarView return new DefaultTabController( length: 2, child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), bottom: new TabBar( tabs: [ new Tab(text: "愛工大ゆき"), new Tab(text: "八草駅ゆき"), ], ), ), body: new TabBarView( children: _pages, ))); } }

Slide 40

Slide 40 text

TabBarView return new DefaultTabController( length: 2, child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), bottom: new TabBar( tabs: [ new Tab(text: "愛工大ゆき"), new Tab(text: "八草駅ゆき"), ], ), ), body: new TabBarView( children: _pages, ))); } }

Slide 41

Slide 41 text

TabBarView return new DefaultTabController( length: 2, child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), bottom: new TabBar( tabs: [ new Tab(text: "愛工大ゆき"), new Tab(text: "八草駅ゆき"), ], ), ), body: new TabBarView( children: _pages, ))); } }

Slide 42

Slide 42 text

TabBarView return new DefaultTabController( length: 2, child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), bottom: new TabBar( tabs: [ new Tab(text: "愛工大ゆき"), new Tab(text: "八草駅ゆき"), ], ), ), body: new TabBarView( children: _pages, ))); } }

Slide 43

Slide 43 text

TabBarView final List _pages = [ Column( children: [ Padding( padding: const EdgeInsets.all(32.0), child: new Text("widget1"), ) ], ), Column( children: [ Padding( padding: const EdgeInsets.all(32.0), child: new Text("widget2"), ) ], ), ];

Slide 44

Slide 44 text

TabBarView

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

自分で作るしか無さそう…

Slide 49

Slide 49 text

独自TabBarを作るには? ● TabBarはPreferredSizeWidgetを実装し ている ● PreferredSizeWidgetを実装したクラスな らいけそう

Slide 50

Slide 50 text

CustomTabBar class CustomTabBar extends StatelessWidget implements PreferredSizeWidget { final TabBar tabBar; final String currentDate; final String currentTimeTable; CustomTabBar({ this.currentDate, this.currentTimeTable, this.tabBar }) : super();

Slide 51

Slide 51 text

CustomTabBar class CustomTabBar extends StatelessWidget implements PreferredSizeWidget { final TabBar tabBar; final String currentDate; final String currentTimeTable; CustomTabBar({ this.currentDate, this.currentTimeTable, this.tabBar }) : super();

Slide 52

Slide 52 text

CustomTabBar class CustomTabBar extends StatelessWidget implements PreferredSizeWidget { final TabBar tabBar; final String currentDate; final String currentTimeTable; CustomTabBar({ this.currentDate, this.currentTimeTable, this.tabBar }) : super();

Slide 53

Slide 53 text

CustomTabBar class CustomTabBar extends StatelessWidget implements PreferredSizeWidget { final TabBar tabBar; final String currentDate; final String currentTimeTable; CustomTabBar({ this.currentDate, this.currentTimeTable, this.tabBar }) : super();

Slide 54

Slide 54 text

CustomTabBar @override Size get preferredSize => new Size(0.0, 90.0); @override Widget build(BuildContext context) { return new Column( children: [ createCurrentStatus(), tabBar ], ); }

Slide 55

Slide 55 text

CustomTabBar @override Size get preferredSize => new Size(0.0, 90.0); @override Widget build(BuildContext context) { return new Column( children: [ createCurrentStatus(), tabBar ], ); }

Slide 56

Slide 56 text

CustomTabBar @override Size get preferredSize => new Size(0.0, 90.0); @override Widget build(BuildContext context) { return new Column( children: [ createCurrentStatus(), tabBar ], ); }

Slide 57

Slide 57 text

CustomTabBar @override Size get preferredSize => new Size(0.0, 90.0); @override Widget build(BuildContext context) { return new Column( children: [ createCurrentStatus(), tabBar ], ); }

Slide 58

Slide 58 text

CustomTabBar Widget createCurrentStatus() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ createExpanded(currentDate), createExpanded(currentTimeTable), ], ); }

Slide 59

Slide 59 text

CustomTabBar Widget createCurrentStatus() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ createExpanded(currentDate), createExpanded(currentTimeTable), ], ); }

Slide 60

Slide 60 text

CustomTabBar Widget createCurrentStatus() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ createExpanded(currentDate), createExpanded(currentTimeTable), ], ); } Widget A (currentDate) Widget B (currentTimeTable)

Slide 61

Slide 61 text

CustomTabBar Expanded createExpanded(String text) { return new Expanded( flex: 1, child: new Container( decoration: new BoxDecoration( border: new Border.all(color: Colors.white) ), child: ...

Slide 62

Slide 62 text

CustomTabBar Expanded createExpanded(String text) { return new Expanded( flex: 1, child: new Container( decoration: new BoxDecoration( border: new Border.all(color: Colors.white) ), child: ...

Slide 63

Slide 63 text

CustomTabBar Expanded createExpanded(String text) { return new Expanded( flex: 1, child: new Container( decoration: new BoxDecoration( border: new Border.all(color: Colors.white) ), child: ...

Slide 64

Slide 64 text

CustomTabBar child: new Padding( padding: const EdgeInsets.all(2.0), child: new MaterialButton( onPressed: () {}, child: new Text( text, style: new TextStyle( color: Colors.white ), ), ), ), ), ); }

Slide 65

Slide 65 text

CustomTabBar child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), bottom: new CustomTabBar( currentDate: "2018年4月16日(月)", currentTimeTable: "Aダイヤ", tabBar: new TabBar( tabs: [ new Tab(text: "愛工大ゆき"), new Tab(text: "八草駅ゆき"), ], ), ), ),

Slide 66

Slide 66 text

CustomTabBar child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), bottom: new CustomTabBar( currentDate: "2018年4月16日(月)", currentTimeTable: "Aダイヤ", tabBar: new TabBar( tabs: [ new Tab(text: "愛工大ゆき"), new Tab(text: "八草駅ゆき"), ], ), ), ),

Slide 67

Slide 67 text

CustomTabBar child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), bottom: new CustomTabBar( currentDate: "2018年4月16日(月)", currentTimeTable: "Aダイヤ", tabBar: new TabBar( tabs: [ new Tab(text: "愛工大ゆき"), new Tab(text: "八草駅ゆき"), ], ), ), ),

Slide 68

Slide 68 text

CustomTabBar child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), bottom: new CustomTabBar( currentDate: "2018年4月16日(月)", currentTimeTable: "Aダイヤ", tabBar: new TabBar( tabs: [ new Tab(text: "愛工大ゆき"), new Tab(text: "八草駅ゆき"), ], ), ), ),

Slide 69

Slide 69 text

CustomTabBar

Slide 70

Slide 70 text

ListView

Slide 71

Slide 71 text

ListView static List createDataList() { List list = List(); for (int i = 0; i < 100; i++) { list.add("test$i"); } return list; }

Slide 72

Slide 72 text

ListView static List createDataList() { List list = List(); for (int i = 0; i < 100; i++) { list.add("test$i"); } return list; }

Slide 73

Slide 73 text

ListView static List createCards(List list) { List cardList = new List(); list.forEach((s) { cardList.add(new Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Text(s), ))); }); return cardList; }

Slide 74

Slide 74 text

ListView static List createCards(List list) { List cardList = new List(); list.forEach((s) { cardList.add(new Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Text(s), ))); }); return cardList; }

Slide 75

Slide 75 text

ListView static ListView createListView() { return new ListView( shrinkWrap: true, padding: const EdgeInsets.all(20.0), children: createCards(createDataList()), ); }

Slide 76

Slide 76 text

ListView static ListView createListView() { return new ListView( shrinkWrap: true, padding: const EdgeInsets.all(20.0), children: createCards(createDataList()), ); }

Slide 77

Slide 77 text

ListView final List _pages = [ createListView(), createListView(), ];

Slide 78

Slide 78 text

ListView

Slide 79

Slide 79 text

3. APIから取得して  Widgetに反映

Slide 80

Slide 80 text

APIからデータを取得する ● モデルを作る ● http.getで取ってくる ● 結果をWidgetに反映

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

http.getで取ってくる Future fetchPost() async { final response = await http.get( 'http://api.syarihu.net/ait_timetable/timetables/'); final responseJson = json.decode(response.body); return new Destinations.fromJson(responseJson); }

Slide 83

Slide 83 text

http.getで取ってくる Future fetchPost() async { final response = await http.get( 'http://api.syarihu.net/ait_timetable/timetables/'); final responseJson = json.decode(response.body); return new Destinations.fromJson(responseJson); }

Slide 84

Slide 84 text

http.getで取ってくる Future fetchPost() async { final response = await http.get( 'http://api.syarihu.net/ait_timetable/timetables/'); final responseJson = json.decode(response.body); return new Destinations.fromJson(responseJson); }

Slide 85

Slide 85 text

結果をWidgetに反映 static FutureBuilder createFutureBuilder( int destination) { return new FutureBuilder( future: fetchPost(), builder: (context, snapshot) { if (snapshot.hasData) { return createListView( snapshot.data.destinations[destination].timetables); } else if (snapshot.hasError) { return new Text("${snapshot.error}"); } return new CircularProgressIndicator(); }, ); }

Slide 86

Slide 86 text

結果をWidgetに反映 static FutureBuilder createFutureBuilder( int destination) { return new FutureBuilder( future: fetchPost(), builder: (context, snapshot) { if (snapshot.hasData) { return createListView( snapshot.data.destinations[destination].timetables); } else if (snapshot.hasError) { return new Text("${snapshot.error}"); } return new CircularProgressIndicator(); }, ); }

Slide 87

Slide 87 text

結果をWidgetに反映 static FutureBuilder createFutureBuilder( int destination) { return new FutureBuilder( future: fetchPost(), builder: (context, snapshot) { if (snapshot.hasData) { return createListView( snapshot.data.destinations[destination].timetables); } else if (snapshot.hasError) { return new Text("${snapshot.error}"); } return new CircularProgressIndicator(); }, ); }

Slide 88

Slide 88 text

結果をWidgetに反映 static FutureBuilder createFutureBuilder( int destination) { return new FutureBuilder( future: fetchPost(), builder: (context, snapshot) { if (snapshot.hasData) { return createListView( snapshot.data.destinations[destination].timetables); } else if (snapshot.hasError) { return new Text("${snapshot.error}"); } return new CircularProgressIndicator(); }, ); }

Slide 89

Slide 89 text

結果をWidgetに反映 static FutureBuilder createFutureBuilder( int destination) { return new FutureBuilder( future: fetchPost(), builder: (context, snapshot) { if (snapshot.hasData) { return createListView( snapshot.data.destinations[destination].timetables); } else if (snapshot.hasError) { return new Text("${snapshot.error}"); } return new CircularProgressIndicator(); }, ); }

Slide 90

Slide 90 text

結果をWidgetに反映 class _MyHomePageState extends State { final List _pages = [ new ConstrainedBox( constraints: const BoxConstraints.expand(), child: createFutureBuilder(0), ), new ConstrainedBox( constraints: const BoxConstraints.expand(), child: createFutureBuilder(1), ), ];

Slide 91

Slide 91 text

結果をWidgetに反映 class _MyHomePageState extends State { final List _pages = [ new ConstrainedBox( constraints: const BoxConstraints.expand(), child: createFutureBuilder(0), ), new ConstrainedBox( constraints: const BoxConstraints.expand(), child: createFutureBuilder(1), ), ];

Slide 92

Slide 92 text

APIから取得してWidgetに反映

Slide 93

Slide 93 text

まとめ

Slide 94

Slide 94 text

まとめ ● 標準のWidgetならわりと簡単になんでも 作れそう ● ちょっとでも凝ったもの作ろうとすると Widgetをカスタマイズしないと ダメそう

Slide 95

Slide 95 text

まとめ ● 通信の部分もWidgetで作るとは思わな かったので本当にすべてはWidget だった(小並感) ● そんなに時間をかけずにある程度作れた ので慣れればかなり良さそう

Slide 96

Slide 96 text

技術書典4 ● 日時 2018/04/22 (日) 11:00〜17:00 ● 場所 秋葉原UDX アキバ・スクエア

Slide 97

Slide 97 text

NOW & FUTURE TechBooster ● Android ● Flutter ● Swift ● Kotlin のコルーチン の動作原理など モバイル技術に注目した 解説書です

Slide 98

Slide 98 text

ありがとうございました