公式のcodelabでFlutterに入門してみた

今度Flutter Meetup Tokyo #2に参加するので、事前にちょっと手を動かしてみました。

flutter-jp.connpass.com

What’s Flutter

Flutter - Beautiful native apps in record time

Googleが開発しているmobile appのSDKで、Dartという言語を用いて開発します。
本家サイトによると、特徴は以下の5つ。

  • Fast development
    • ホットリロードが可能
  • Expressive and Flexible UI
  • Modern, reactive framework
  • Access native features and SDKs
    • プラットフォーム(OS)のAPIや3rd partyのSDK、ネイティブのコードを呼び出せる(素敵)
  • Unified app development
    • AndroidiOSも、プラットフォームの差を意識することなく開発可能
    • Mobile Appの開発経験がなくても大丈夫!
    • 開発、テスト、デバッグコンパイル、配布に至るまでサポートするツールが充実している

環境構築

環境

1. install Dart

$ brew tap dart-lang/dart

==> Tapping dart-lang/dart
Cloning into '/usr/local/Homebrew/Library/Taps/dart-lang/homebrew-dart'...
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 2 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
Tapped 1 formula (30 files, 26.3KB)

$ brew install dart --with-dartium --with-content-shell

==> Installing dart from dart-lang/dart
==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/sdk/dartsdk-macos-x64-release.zip
######################################################################## 100.0%
==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/dartium/dartium-macos-x64-release.zip
######################################################################## 100.0%
==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/dartium/content_shell-macos-x64-relea
######################################################################## 100.0%
==> Caveats
Please note the path to the Dart SDK:
  /usr/local/opt/dart/libexec
==> Summary
🍺  /usr/local/Cellar/dart/1.24.3: 3,039 files, 657.7MB, built in 21 seconds

2. install Flutter

$ git clone https://github.com/flutter/flutter.git

Cloning into 'flutter'...
remote: Counting objects: 120517, done.
remote: Total 120517 (delta 0), reused 0 (delta 0), pack-reused 120516
Receiving objects: 100% (120517/120517), 36.60 MiB | 3.11 MiB/s, done.
Resolving deltas: 100% (90418/90418), done.

$ git co v0.3.1 # flutter公式ページのzip最新版がv0.3.1だったため(2018/04/29現在)
$ vim ~/.zprofile

export PATH=$HOME/flutter/bin:$PATH 

3. Set up environment

$ flutter doctor

Downloading Dart SDK from Flutter engine 09d05a38912a3c1a906e95099cac9a7e14fae85f...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 43.9M  100 43.9M    0     0  9507k      0  0:00:04  0:00:04 --:--:-- 9508k
Building flutter tool...

  ╔════════════════════════════════════════════════════════════════════════════╗
  ║                 Welcome to Flutter! - https://flutter.io                   ║
  ║                                                                            ║
  ║ The Flutter tool anonymously reports feature usage statistics and crash    ║
  ║ reports to Google in order to help Google contribute improvements to       ║
  ║ Flutter over time.                                                         ║
  ║                                                                            ║
  ║ Read about data we send with crash reports:                                ║
  ║ https://github.com/flutter/flutter/wiki/Flutter-CLI-crash-reporting        ║
  ║                                                                            ║
  ║ See Google's privacy policy:                                               ║
  ║ https://www.google.com/intl/en/policies/privacy/                           ║
  ║                                                                            ║
  ║ Use "flutter config --no-analytics" to disable analytics and crash         ║
  ║ reporting.                                                                 ║
  ╚════════════════════════════════════════════════════════════════════════════╝
  
Downloading Material fonts...                                0.9s
Downloading package sky_engine...                            0.5s
Downloading common tools...                                  1.1s
Downloading darwin-x64 tools...                              1.9s
Downloading android-arm-profile/darwin-x64 tools...          0.7s
Downloading android-arm-release/darwin-x64 tools...          0.8s
Downloading android-arm64-profile/darwin-x64 tools...        0.8s
Downloading android-arm64-release/darwin-x64 tools...        0.8s
Downloading android-x86 tools...                             1.8s
Downloading android-x64 tools...                             1.5s
Downloading android-arm tools...                             1.6s
Downloading android-arm-profile tools...                     1.0s
Downloading android-arm-release tools...                     1.2s
Downloading android-arm64 tools...                           1.3s
Downloading android-arm64-profile tools...                   1.1s
Downloading android-arm64-release tools...                   1.0s
Downloading ios tools...                                     2.2s
Downloading ios-profile tools...                             2.1s
Downloading ios-release tools...                             1.9s
Downloading Gradle Wrapper...                                0.2s
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel unknown, v0.3.1, on Mac OS X 10.13.4 17E199, locale ja-JP)
-Error executing simctl: -6
dyld: Symbol not found: _SimDeviceBootKeyDisabledJobs
  Referenced from: /Applications/Xcode.app/Contents/Developer/usr/bin/simctl
  Expected in: /Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/CoreSimulator
 in /Applications/Xcode.app/Contents/Developer/usr/bin/simctl

[!] Android toolchain - develop for Android devices (Android SDK 27.0.3)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses
[!] iOS toolchain - develop for iOS devices (Xcode 9.3)
    ✗ Xcode requires additional components to be installed in order to run.
      Launch Xcode and install additional required components when prompted.
    ✗ libimobiledevice and ideviceinstaller are not installed. To install, run:
        brew install --HEAD libimobiledevice
        brew install ideviceinstaller
    ✗ ios-deploy not installed. To install:
        brew install ios-deploy
    ✗ CocoaPods not installed.
        CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
        Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS.
        For more info, see https://flutter.io/platform-plugins
      To install:
        brew install cocoapods
        pod setup
[✓] Android Studio (version 3.1)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[!] IntelliJ IDEA Community Edition (version 2017.2.6)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] VS Code (version 1.22.2)
[!] Connected devices
    ! No devices available

! Doctor found issues in 4 categories.

いくつか引っかかりました。

Android license

  • Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

$ flutter doctor --android-licenses

3 of 6 SDK package licenses not accepted. 100% Computing updates...             
Review licenses that have not been accepted (y/N)? y

1/3: License android-googletv-license:
2/3: License google-gdk-license:
3/3: License mips-android-sysimage-license:

iOS

  • Xcodeのcomponents
  • gem
    • libimobiledevice
    • ideviceinstaller
    • ios-deploy
    • CocoaPods

CocoaPods入れてなかったのがつらい。。。30分休み。

Android Studio

  • Flutter plugin
  • Dart plugin

Device

  • Device繋がってないよ!はい。

対応し、再挑戦してみます。

$ flutter doctor

master
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel unknown, v0.3.1, on Mac OS X 10.13.4 17E199, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 9.3)
[✓] Android Studio (version 3.1)
[✓] VS Code (version 1.22.2)
[✓] Connected devices (2 available)

いい感じです。これで開発環境が整いました。

codelabについて

flutter.io

スタートアップの会社名候補を生成するシンプルなモバイルアプリを作っていきます。お気に入り機能があり、別のページでお気に入りの名前リストを表示することもできます。
f:id:t-miliya612:20180429233102p:plain

開発はAndroidStudioを使いましたが、VScodeなどを使っても良いそうです。

Step 1: Create the starting Flutter app

Flutterのプロジェクトを新規生成します。
Android Studioを使う場合はウィザードから、VScodeを使う場合はコマンドからアプリの雛形を生成できます。

f:id:t-miliya612:20180429124315p:plain
f:id:t-miliya612:20180429124335p:plain
f:id:t-miliya612:20180429124343p:plain
f:id:t-miliya612:20180429124355p:plain

ファイル構成はこんな感じです。

├── README.md
├── android # androidネイティブのコード
├── build
├── ios # iOSネイティブのコード
├── lib
│   └── main.dart # 手を加えていくファイル
├── pubspec.lock
├── pubspec.yaml # 依存関係などなどを管理するファイル
└── test
    └── widget_test.dart

終わったら、codelabにあるコードでlib/main.dartを置き換えましょう。

Step 2: Use an external package

english_wordsというプラグインを入れます。プラグインの追加は、以下の手順で行います。

  1. pubspec.yamlに追記
    • english_words: ^3.1.0
  2. Android Studioのタブに表示されているPackages Getをクリック f:id:t-miliya612:20180429124552p:plain
    • 以下が表示されればOK
${PATH_TO_FLUTTER}/flutter/bin/flutter --no-color packages get
Running "flutter packages get" in ${YOUR_APP_NAME}...
Process finished with exit code 0
  1. lib/main.dartにimportを追記
    • import 'package:english_words/english_words.dart';

後はいつも通り使うだけです。特に難しいところはなさそうです。

Step 3: Add a Stateful widget

WidgetにはStatefulとStatelessの2種類があります。画面を表示している間にWidgetを書き換えるかどうかで使い分けます。今回のアプリでは無限スクロールのListを描画するので、StatefulWidgetを拡張したクラスを使用します。
まずは中央に表示されたtextを試しにstatefulにしてみます。

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          child: new RandomWords(),
        ),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }
}

Step 4: Create an infinite scrolling ListView

いよいよ無限スクロールのListViewを生成します。ListView.builder()を返すWidgetを定義し、builderの引数として渡すitemBuilderプロパティの中でリスト生成のロジックを記述します。

(省略...)
class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _biggerFont = const TextStyle(fontSize: 18.0);
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );
  }
  Widget _buildSuggestions() {
    return new ListView.builder( // ListView.builderを生成
      padding: const EdgeInsets.all(16.0),
      itemBuilder: (context, i){ // itemBuilderのプロパティにリストを描画する無名関数を定義
        if (i.isOdd) return new Divider();
        final index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      },
    );
  }

  // リスト1行をセットする
  Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
}

Step 5: Add interactivity

ハート型アイコンをセットして、タップするとお気に入り登録できるようにします。Iconsクラスがすごく充実していて、アイコン追加がかなり楽そうです。

docs.flutter.io

Androidで言うonClickメソッドは、onTapプロパティに対応します。

(省略...)
  Widget _buildRow(WordPair pair) {
    final alreadySaved = _saved.contains(pair);
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      onTap: () {
        setState(() {
          if (alreadySaved) {
            _saved.remove(pair);
          } else {
            _saved.add(pair);
          }
        });
      },
    );
  }

Step 6: Navigate to a new screen

画面遷移のコードを書いていきます。Flutterでは新しい画面のことをrouteと言うみたいですね。
routeのスタックは、Navigationクラスを使って管理していきます。

(省略...)
  void _pushSaved(){
    Navigator.of(context).push(
      new MaterialPageRoute(
          builder: (context) {
            // お気に入り登録されたリストを各行にマップ
            final tiles = _saved.map( 
                (pair) {
                  return new ListTile(
                    title: new Text(
                      pair.asPascalCase,
                      style: _biggerFont,
                    ),
                  );
                },
            );
            // リストを生成
            final divided = ListTile
              .divideTiles(
                  context: context,
                  tiles: tiles,
              )
              .toList();
            // appBarと画面のコンテンツ(リスト)をセット
            return new Scaffold(
              appBar: new AppBar(
                title: new Text('Saved Suggestions'),
              ),
              body: new ListView(children: divided),
            );
          },
      ),
    );
  }

Step 7: Change the UI using Themes

UIの色をいじってみます。今回はひとまず、primaryColorを変更してappBarの色を変えてみます。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      theme: new ThemeData(
        primaryColor: Colors.white,
      ),
      home: new RandomWords(),
    );
  }
}

簡単ですね!ただ、結構大胆にカスタマイズをしようとすると大変なことになりそうな。。。
MaterialDesignからどうしても離れなきゃいけないときは厳しそうですね。

作ったもの

github.com

f:id:t-miliya612:20180429233141p:plain
f:id:t-miliya612:20180429233147p:plain

まとめ

一通りアプリを完成させるところまでやってみました。かなり簡単にそれなりのUIを作成することができました。
イメージとしてはAnkoのような感じかなーと思っていたのですが、ちょっと違う印象でした。ReactやVueなどのライブラリは使ったことがないのですが、そういったjsのライブラリで培ったノウハウが必要になりそうです。

とはいえ、これでFlutterのことが何となく完全に理解できました。今からmeetupが楽しみです。

今後やりたいこと

  • ファイル構成を考える
    • ファイルってどうやって分けていくんだろ
    • いい加減Reactやらねば。。。
  • OS依存の機能を使う
    • network通信
    • GPS
    • camera
    • SMS(出来るかな……?)