Flutterで始めるアプリ開発
Flutterで始めるアプリ開発
Flutterを使ったiOS/Android/Webアプリ開発への入門に必要な情報を
分かりやすく紹介するウェブサイトを立ち上げました。
よかったら利用してみて下さい。
ウェブサイト概要
ウェブサイトの対象者
- アプリを作ってみたいけど、どうやって作るか分からない方 😕
- iOS/Androidのアプリ開発が難しすぎて挫折してしまった方 😨
- Flutterを使って簡単にアプリを作れるようになりたい方 😆
- などなど
ウェブサイトのゴール
FlutterとFirebaseを組み合わせた、
少し複雑なiOS/Android/Webアプリ開発ができる状態に持っていくことを目指します 😀
ウェブサイトで紹介する内容
- Flutterとは
- Flutterを使ってみる
- Widgetを使ってみる
- Todoアプリを作ってみる
- Firebaseとの連携
- 少し複雑なアプリ作成
- 自分だけのWebアプリ公開
更新情報
まだまだ、作成できてないページがあったり、分かりづらい部分も多々あります。。。
新しく追加したページや更新した部分は随時Twitterで共有していくつもりなので、
よかったらこちらもチェックしてみて下さい。
Flutter for Web
Flutter for Web
Flutter for Web
- Android/iOSに加えてFlutterの対応プラットフォームにWebが入っている
- Dartで書かれたソースコードをJavaScriptへと変換し動作させる
- 2020年4月時点では beta channel で使用可能(本番利用は非推奨)
使い方
仕組み
- DartからJavaScriptへの変換処理はDart自体で提供されている仕組みを利用
- DOM・Canvas・CSSを使い各ブラウザで動作する描画処理をFlutter側で提供している
Dart Web
- webdevを使いDartからJavaScriptへの変換処理を行う
- 内部では、dartdevc と daert2js の2つのコンパイラが使われている
- dartdevc
- the Dart dev compiler
- 開発用、差分ビルド等が使える
- dart2js
- Dart-to-JavaScript compiler
- 本番用
- HTMLから変換されたJavaScriptを呼び出すことで処理を実行する
DartでWebアプリケーション作成
$ dart --version Dart VM version: 2.7.2 (Mon Mar 23 22:11:27 2020 +0100) on "macos_x64" // DartからJavaScriptへの変換処理を行うためのツール $ pub global activate webdev // プロジェクト作成 $ mkdir dartwebapp $ cd dartwebapp $ vi pubspec.yaml name: dartwebapp description: Dart web application environment: sdk: '>=2.7.0 <3.0.0' dev_dependencies: # dependencies for webdev build_runner: ^1.8.1 build_test: ^0.10.12+1 build_web_compilers: ^2.9.0 $ vi web/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>dartwebapp</title> <!-- JavaScriptに変換されたmain.dartを読み込む --> <script defer src="main.dart.js"></script> </head> <body> <div id="hello"></div> </body> </html> $ vi web/main.dart import 'dart:html'; void main() { querySelector('#hello').text = 'Hello World!!'; } // Webアプリケーション起動(開発用) $ pub global run webdev serve ... [INFO] Serving `web` on http://127.0.0.1:8080 ... // ビルド(本番用) $ pub global run webdev build --release
Flutterでの描画処理
- HTML・CSS・Canvasを組み合わせて描画を行っている
DomCanvasとBitmapCanvasの使われ方
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( leading: FlutterLogo(), ), body: Center( child: Text('Hello World'), ), ), ); } }
<flt-scene flt-layer-state="updated" style="position: absolute;"> <flt-transform flt-layer-state="updated" style="position: absolute; transform-origin: 0px 0px 0px;"> <flt-offset flt-layer-state="retained" style="position: absolute; transform-origin: 0px 0px 0px; transform: translate(0px, 0px);"> <flt-offset flt-layer-state="retained" style="position: absolute; transform-origin: 0px 0px 0px; transform: translate(0px, 0px);"> <flt-clip flt-layer-state="retained" clip-type="physical-shape" style="position: absolute; overflow: hidden; background-color: rgb(250, 250, 250); box-shadow: none; left: 0px; top: 0px; width: 552px; height: 815px; border-radius: 0px;"> <flt-clip-interior style="position: absolute; left: 0px; top: 0px;"> <flt-picture flt-layer-state="retained" style="position: absolute; transform: translate(0px, 0px);"> <!-- HTML&CSSのみで描画できる場合 → HTML&CSSを使う (DomCanvas) --> <flt-dom-canvas style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px;"> <!-- Scaffold.body --> <p style="font-size: 14px; font-weight: normal; font-family: Roboto, Arial, sans-serif; color: rgba(0, 0, 0, 0.867); position: absolute; white-space: pre-wrap; overflow-wrap: break-word; overflow: hidden; height: 16px; width: 72px; transform-origin: 0px 0px 0px; transform: matrix(1, 0, 0, 1, 240, 427.5);">Hello World</p> </flt-dom-canvas> </flt-picture> <flt-clip flt-layer-state="retained" clip-type="physical-shape" style="position: absolute; overflow: hidden; background-color: rgb(33, 150, 243); box-shadow: rgba(0, 0, 0, 0.4) 1.33333px 2.66667px 5.52px 0px; left: 0px; top: 0px; width: 552px; height: 56px; border-radius: 0px;"> <flt-clip-interior style="position: absolute; left: 0px; top: 0px;"> <flt-picture flt-layer-state="retained" style="position: absolute; transform: translate(0px, 0px);"> <!-- HTML&CSSのみで描画できない場合 → Canvasを使う (BitmapCanvas) --> <flt-canvas style="position: absolute; transform: translate(6px, 1px);"> <!-- Scaffold.appBar --> <canvas width="44" height="54" style="position: absolute; width: 44px; height: 54px; z-index: -1;"></canvas> </flt-canvas> </flt-picture> </flt-clip-interior> </flt-clip> <flt-picture flt-layer-state="retained" style="position: absolute; transform: translate(0px, 0px);"></flt-picture> </flt-clip-interior> </flt-clip> </flt-offset> </flt-offset> </flt-transform> </flt-scene>
Nativeコードを呼び出す(JavaScriptを呼び出す)
- Android/iOSとは異なりFlutter for WebではDartからJavaScriptに変換して実行されるため、DartからJavaScriptに変換するための型を定義することで任意のJavaScriptの処理を呼び出すことができる
package:js
を使用し変換のための型を定義する
参考資料
Flutter Widget of the Week まとめ 2
Flutter Widget of the Week
Flutter Widget of the Week で紹介されたWidgetを実際に使ってみたいと思います。
FittedBox
LayoutBuilder
AbsorbPointer
Transform
BackdropFilter
Align
Positioned
AnimatedBuilder
Dismissible
SizedBox
ValueListenableBuilder
Draggable
AnimatedList
Flexible
MediaQuery
Spacer
InheritedWidget
AnimatedIcon
AspectRatio
LimitedBox
Placeholder
Flutter - InheritedWidgetを使ってProviderを実装してみる
Provider
- ProviderとはInheritedWidgetをラッパーし使いやすくした、状態管理をするためのライブラリである。 github.com
InheritedWidget
- 特定の子ウィジェットのみに変更を伝搬したい時に使えるウィジェットである。
- 詳細は下記記事の内容が参考になる medium.com
InheritedWidgetを使ってProviderを実装してみる
InheritedWidget
を使い、Provider.of()
とProvider.value()
を実装してみた
感想
- provider を使うのであれば、InheritedWidgetの理解を深めておくと良さそう
StatefulWidget
にvalue
をもたせていない場合にうまく動作しなかったが原因はよくわかっていない- provider v3 では
StatefulWidget
を使った実装だが、v4 から使われなくなっている- 軽く実装を見てみたが、どのようにして
ChangeNotifier
の変更を検知し再ビルドしているのか分からなかった
- 軽く実装を見てみたが、どのようにして
Flutter Widget of the Week まとめ 1
Flutter Widget of the Week
Flutter Widget of the Week で紹介されたWidgetを実際に使ってみたいと思います。
Flutter Widget of the Week - YouTube
SafeArea
Expanded
Wrap
AnimatedContainer
Opacity
FutureBuilder
FadeTransition
FloatingActionButton
PageView
Table
SliverAppBar
SliverList & SliverGrid
FadeInImage
StreamBuilder
InheritedModel
ClipRRect
Hero
CustomPaint
Tooltip
Flutter関連情報まとめ
YouTube
- Flutter Channel
ブログ
- Flutter Community
- Flutter 🇯🇵
イベント
- Flutter Japan User Group
- Flutter Interact
- Google I/O
プロトタイピング
- Adobe XD to Flutter Plugin
- Supernova
Dart
開発ツール
- Dart DevTools
まとめ
Flutter入門
Flutterとは
Flutterとは
- Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
特徴
- Fast development
- ホットリロードを始めとした、より高速に開発するための様々なツールが提供されている
- Expressive, beautiful UIs
- Material Design 等に沿ったUIパーツを使うことで、いい感じのUIが簡単に作れる
- Native Performance
- Dartという言語を使い、各プラットフォームに応じたネイティブコードを生成できる
なぜFlutterを使うのか?
- より高速に開発するため
- より魅力的なUI/UXを提供するため
アーキテクチャ・コンセプト
- 独自エンジンを各プラットフォームで動かせるようにしている
- エンジン内部にはUI描画処理も含まれている >> Skia
- ウィジェットのツリーでUIを表現
- Reactのような宣言的UIを採用している
Flutter for Web
iOS/AndroidだけでなくWebアプリの開発もサポートされている。
(2020年5月時点ではβ版)
Flutterでアプリを開発するには
Flutterを使ったアプリ開発の入門サイトがあるので、
こちらを利用してみましょう。
Androidアプリを起動してみる
Flutter導入
プロジェクト作成
// プロジェクト作成 w/ Integration tests $ flutter create --org com.umatoma --with-driver-test --no-pub flutter_training_app // そのままだと依存解決に失敗するので調整 $ vim pubspec.yaml - test: 1.6.2 + test: ^1.9.4 $ flutter pub get
アプリ起動
// 利用可能なエミュレータ一覧を確認 $ flutter emulators 3 available emulators: Pixel_3a_API_28 • Pixel 3a API 28 • Google • android TEST • TEST • • android apple_ios_simulator • iOS Simulator • Apple • ios ... $ flutter emulators --launch Pixel_3a_API_28 $ flutter run
テスト実行
// Unit tests 実行 // ファイル名が *_test.dart となっているものをテストファイルと認識する $ flutter test test/ 00:06 +1: All tests passed! // Integration tests 実行 // test_driver ディレクトリに target と同期したファイル名のテストが実行される // e.g. main.dart >> test_driver/main_test.dart $ flutter drive --target=./lib/main.dart Using device AOSP on IA Emulator. Starting application: ./lib/main.dart Installing build/app/outputs/apk/app.apk... 4.3s Running Gradle task 'assembleDebug'... Running Gradle task 'assembleDebug'... Done 13.2s ✓ Built build/app/outputs/apk/debug/app-debug.apk. ... 00:00 +0: end-to-end test (setUpAll) ... 00:01 +0: end-to-end test tap on the floating action button; verify counter 00:01 +1: end-to-end test (tearDownAll) 00:01 +1: All tests passed! Stopping application instance.
リリース用ビルド作成
// keystore 作成 $ keytool -genkey -v -keystore ./key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key // 署名用の設定を追加 $ vim android/app/build.gradle // apk 作成 $ flutter build apk You are building a fat APK that includes binaries for android-arm, android-arm64, android-x64. ... Removed unused resources: Binary resource data reduced from 37KB to 32KB: Removed 14% Running Gradle task 'assembleRelease'... Running Gradle task 'assembleRelease'... Done 14.9s ✓ Built build/app/outputs/apk/release/app-release.apk (15.6MB).
Webアプリを起動してみる
Webアプリ機能有効化
// 現時点ではbeta版らしい $ flutter channel beta $ flutter config --enable-web // 作成済みのプロジェクトにWebアプリのコードを追加する場合 $ flutter create . Recreating project .... web/index.html (created) web/manifest.json (created) web/icons/Icon-192.png (created) web/icons/Icon-512.png (created) Wrote 7 files. ...
アプリ起動
$ flutter run -d chrome
リリース用ビルド作成
$ flutter build web
状態管理はどうやるのか
State
class MyWidget extends StatefulWidget { @override _MyWidgetState createState() => _MyWidgetState(); } class _MyWidgetState extends State<MyWidget> { int _count = 0; @override Widget build(BuildContext context) { return Container( child: RaisedButton( onPressed: () { setState(() { _count++; }); }, child: Text('Increment: $_count'), ), ); } }
Provider
void main() { runApp(ChangeNotifierProvider( create: (context) => CountModel(), child: MaterialApp( home: Column( children: <Widget>[ CounterText(), CounterButton(), ], ), ), )); } class CountModel extends ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } } class CounterText extends StatelessWidget { @override Widget build(BuildContext context) { // Consumer を通して Provider に渡したモデルにアクセスできる return Consumer<CountModel>( builder: (context, countModel, child) => Text('Count: ${countModel.count}'), ); } } class CounterButton extends StatelessWidget { @override Widget build(BuildContext context) { // データを参照する必要がない場合は Provider.of で listen:false でもOK final countModel = Provider.of<CountModel>(context, listen: false); return RaisedButton( onPressed: () => countModel.increment(), child: Text('Increment'), ); } }
ProviderはInheritedWidgetを使いやすくしたものである。
Providerの仕組みに関してはこちら↓で紹介
Widget
FlutterではUIを構築するための様々なWidgetが提供されている。
様々なWidgetを使ってみたい場合はこちら↓から
umatomakun.hatenablog.com umatomakun.hatenablog.com
感想
- 軽く使ってみた感じ、非常に簡単に環境構築〜アプリ作成まで行えるのが良い
- エディタを含め開発環境周りもそれなりに整っており、全体としての完成度が非常に高い印象
- Reactに影響されてそうな部分が多く、Reactユーザーにとっては参入障壁が低いはず
- テスト周りもデフォルトで Unit・Widget・Integration テストを行える環境が整備されていて良い
- バックエンドにDartを採用できれば、アプリ〜バックエンドまで少人数かつ1チームで全てを網羅することで出来る可能性も
- これができれば、規模が拡大してもFeatureチーム単位で組織を分割しやすくなる?
- ただし、各プラットフォームに関する知識をもっていないと、ハマった時に大変そう...