うまとま君の技術めも

2015年新卒入社した社畜の勉強内容などなど

Flutter入門

Flutterとは

Flutterとは

  • Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
    • 単一のコードベースでいい感じのネイティブアプリが作れるもの
    • Google
    • iOS, Android, (Web, Desktop) をサポート

特徴

  • 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月時点ではβ版)

umatomakun.hatenablog.com

Flutterでアプリを開発するには

Flutterを使ったアプリ開発の入門サイトがあるので、
こちらを利用してみましょう。

www.flutter-study.dev

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

  • Widgetは常にImmutableである
  • 状態管理はStateで管理し、変更に応じてWidgetを再生成する
    • setState() をCallすることで、状態が変更されたことを通知する
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

  • provider パッケージを使うことで、複数Widgetを跨いだ状態の管理が簡単に行える
    • Provider/Consumer が Pub/Sub の関係
    • 任意のWidgetに状態を共有できる
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の仕組みに関してはこちら↓で紹介

umatomakun.hatenablog.com

Widget

FlutterではUIを構築するための様々なWidgetが提供されている。
様々なWidgetを使ってみたい場合はこちら↓から

umatomakun.hatenablog.com umatomakun.hatenablog.com

感想

  • 軽く使ってみた感じ、非常に簡単に環境構築〜アプリ作成まで行えるのが良い
  • エディタを含め開発環境周りもそれなりに整っており、全体としての完成度が非常に高い印象
  • Reactに影響されてそうな部分が多く、Reactユーザーにとっては参入障壁が低いはず
  • テスト周りもデフォルトで Unit・Widget・Integration テストを行える環境が整備されていて良い
  • バックエンドにDartを採用できれば、アプリ〜バックエンドまで少人数かつ1チームで全てを網羅することで出来る可能性も
    • これができれば、規模が拡大してもFeatureチーム単位で組織を分割しやすくなる?
  • ただし、各プラットフォームに関する知識をもっていないと、ハマった時に大変そう...

参考情報