10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FlutterAdvent Calendar 2019

Day 24

フロントエンドエンジニアがFlutterを触ってみた

Last updated at Posted at 2019-12-26

この記事はFlutter Advent Calendar 2019の24日目の記事です。
投稿が遅くなってすみません。

普段はWebのフロントエンドをやっていますが、最近、Flutterをよく聞くのでちょっと触ってみようかと思って勢いでアドベントカレンダーに登録しました!Flutterの知識は名前聞いたことあるくらいでほぼゼロです。

とりあえず、インストールしてサンプルアプリを少し改変してみたという内容の記事ですが、フロントエンドエンジニアの視点から書いてみるので参考になれば幸いです。

作業環境

  • macOS High Sierra
  • Android Studio 3.5.3

インストール

オフィシャルの手順を参考に構築しました。

必要なシステム要件などが書いてありますが、Macだとほとんど標準で入ってるもののようなので、特にこの時点では他に何かをインストールしたりはしませんでした。

SDKをダウンロードして解凍。公式に倣って development フォルダを作成してそこに配置しました。

$ mkdir ~/development
$ cd ~/development
$ unzip ~/Downloads/flutter_macos_v1.12.13+hotfix.5-stable.zip

パスを通します。 .bash_profile に以下の記述を追加。

.bash_profile
export PATH=$PATH:~/development/flutter/bin

ターミナルを再起動。

$ exec $SHELL -l

以下のコマンドを叩いて /Users/{$USER}/development/flutter/bin/flutter が返って来ればOK。

$ which flutter

flutter doctor というコマンドを叩くと、関連するツールの状況が表示されるようです。
環境が整っている項目は[✓]で、ない箇所については [✗] か [!] と表示されます。

$ flutter doctor

細かい説明のところは削ってますが、僕の環境ではこうなりました。

$ [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.13.6 17G65, locale ja-JP)
$ [✗] Android toolchain - develop for Android devices
$ [✗] Xcode - develop for iOS and macOS
$ [!] Android Studio (not installed)
$ [!] VS Code (version 1.41.1)
$ [!] Connected device

Xcodeはインストールされているのですが、古いバージョンのために[✗]になっているのかと思います。
諸事情でOSのバージョンが古いため最新のXcodeがインストールできず今回は諦めました。

iOS開発も試したかったのですが、今回はAndroidの開発を試しすのでAndroid studioをインストールしました。

もう一度 flutter doctor を実行してみると

$ [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.13.6 17G65, locale ja-JP) 
$ [!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    ✗ Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses
$ [✗] Xcode - develop for iOS and macOS
$ [!] Android Studio (version 3.5)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
$ [!] VS Code (version 1.41.1)
$ [!] Connected device

Android toolchain はライセンスの問題で [!] になっているようだったので

$ flutter doctor --android-licenses

でライセンスに同意。

Android StudioはFlutterのプラグインが必要なようだったので、Android Studioを立ち上げて、Preference → plugin → Flutter でプラグインをインストール。この時に、Dartのプラグインも必要と聞かれるので一緒にインストールします。

ここまでやると、この状態になりました。

[✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.13.6 17G65, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✗] Xcode - develop for iOS and macOS
[✓] Android Studio (version 3.5)
[!] VS Code (version 1.41.1)
    ✗ Flutter extension not installed; install from
      https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[!] Connected device
    ! No devices available

VS Code は今回は使わないのでOK。 Connected device もエミュレータを使うのでOKです。

これで開発する準備ができたので、Android Studioを立ち上げます。

プロジェクト作成

01.png

Start a new Flutter project を選択。

02.png

Flutter Applicationを選択。

03.png

プロジェクト名や保存場所を設定。今回はそのまま進めました。

04.png

Company domainは アプリのリリースの時にバンドルIDとして使われるもののようなので、今回はそのままexample.comで進めます。

05.png

プロジェクトが立ち上がりました!

エミュレータの設定

06.png

メニュー → Tools → AVD Managerを選択。
Your Virtual Devicesという画面が立ち上がるのでCreate Virtual Deviceからデバイスを追加します。

08.png

09.png

端末を選択してシステムをダウンロード。
途中、デフォルトの端末の向きの設定やライセンスの同意など求められましたが、全てデフォルトの設定で進めました。

11.png

Your Virtual Devicesの画面に追加した端末が表示されているので、右側の再生ボタンを押すとエミュレータが立ち上がります!

12.png

Android studioの上部のツールバーで Android SDK built for x86(mobile)が選択できるようになっているので選択。
main.dart を選択して再生ボタンを押すとサンプルアプリが起動します。

13.png

初回の実行は、数分時間がかかりました。

サンプルアプリを触ってみる

Webのフロントエンドは、HTML/CSS/JSを使って書きますが、FlutterはDartだけで書いていくようです。
サンプルアプリの main.dart はコメントがたくさん入っているのですごく複雑に見えましたが、コメントを削除するとこんな感じ。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

このサンプルアプリが何をやっているのかは、Flutter はじめの一歩がめちゃくちゃ詳しく書いてくれています。

この記事によると Widget(UIレイアウトの設計図)を組み合わせてUIの階層構造(HTMLタグの入れ子に近い)を構築していくというのが本質 と書いてあって、WidgetがHTMLでいうタグみたいなものかなと思いました。

実際にIDEの右側の Flutter InspectorWidgets のタブを見てみると、なるほど、かなりHTMLっぽいです。ここでだいぶ抵抗が薄れました。

14.png

Widetを追加してみる

テキストを増やしてみました。

main.dart
children: <Widget>[
    Text(
      'これはFlutterのサンプルアプリです',
    ),
    Text(
      'You have pushed the button this many times:',
    ),
    Text(
      '$_counter',
      style: Theme.of(context).textTheme.display1,
    ),
],

15.png

ここは思った通りの挙動。
ホットリロードで、保存するだけで反映してくれるのが便利です!

テキストのスタイルを変えてみる

要素の追加ができたので、スタイルを変えてみます。

main.dart
Text(
	'これはFlutterのサンプルアプリです',
	style: TextStyle(
		fontSize: 24,
		fontWeight: FontWeight.bold,
		color: Colors.red
	)
),

16.png

Text Widgetの引数にCSSっぽい感じで記述すれば変更できるようです。
とはいえ、プロパティの記述の仕方がわからなくて、ここですごく時間かかりました。

ですが、これだとインラインスタイルを書いているような感じなので、使いまわすことを考えるとclassのようにしたい。
オブジェクト(?)っぽい変数を作って、それを渡せば良いみたいです。

main.dart
Widget build(BuildContext context) {

	var largeText = TextStyle(
	    fontSize: 24,
	    fontWeight: FontWeight.bold,
	    color: Colors.red
	);

	// 省略

	Text(
      'これはFlutterのサンプルアプリです',
      style: largeText
    ),

}

デフォルトで入っているカウンターの大きい文字のところは、最初に読み込んでいるマテリアルデザイン用のUIコンポーネントから参照しているようです。

main.dart
style: Theme.of(context).textTheme.display1,

機能を追加してみる

サンプルは、数字を増やすカウンターアプリですが、減らす機能と、0にリセットする機能を追加してみました。
とりあえずボタンを追加します。ありそうだなと思って、Butto まで打ったら色々と候補が出てきたので FlatButton というのを使ってみました。

18.png

main.dart
child: Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Text(
      'これはFlutterのサンプルアプリです',
      style: largeText
    ),
    Text(
      'You have pushed the button this many times:',
    ),
    Text(
      '$_counter',
      style: Theme.of(context).textTheme.display1,
    ),
    FlatButton(
      color: Colors.blue,
      child: Text(
        '足す',
        style: TextStyle(
          color: Colors.white
        )
      )
    ),
    FlatButton(
      color: Colors.blue,
      child: Text(
        '引く',
        style: TextStyle(
          color: Colors.white
        )
      )
    ),
    FlatButton(
      color: Colors.blue,
      child: Text(
        'リセット',
        style: TextStyle(
          color: Colors.white
        )
      )
    )
  ],
),

こんな感じになりました。

17.png

ちなみに Row というWidgetがあるのを確認したので、横並びのボタンにできるかなと思い、こんな感じで書いてみたのですがエラーになりました。この辺はもうちょっと書き方を理解しないとダメですね…

main.dart
body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text(
        'これはFlutterのサンプルアプリです',
        style: largeText
      ),
      // …
    ]
  ),
  child: Row(
    children: <Widget>[
      FlatButton(
        color: Colors.blue,
        child: Text(
          '足す',
          style: TextStyle(
            color: Colors.white
          )
        )
      ),
      // …
    ],
  ),
)

それぞれのボタンに関数をセットしていけばできるだろうというところで、元々のフローティングボタンに記載があったこの部分を参考にします。

main.dart
int _counter = 0;

void _incrementCounter() {
  setState(() {
    _counter++;
  });
}

// …

floatingActionButton: FloatingActionButton(
  onPressed: _incrementCounter,
  tooltip: 'Increment',
  child: Icon(Icons.add),
), 

_incrementCounter という関数があって、フローティングボタンの onPressed に設定してるみたいですね。
この辺は、HTML要素に対してJSのイベントを設定しているような感じでわかりやすいです。

これを参考に、関数を作ってそれぞれのイベントに設定します。

main.dart
void _incrementCounter() {
  setState(() {
    _counter++;
  });
}

void _decrementCounter() {
  setState(() {
    _counter--;
  });
}

void _resetCounter() {
  setState(() {
    _counter = 0;
  });
}

// …

FlatButton(
  onPressed: _incrementCounter,
  color: Colors.blue,
  child: Text(
    '足す',
    style: TextStyle(
      color: Colors.white
    )
  )
),
FlatButton(
  onPressed: _decrementCounter,
  color: Colors.blue,
  child: Text(
    '引く',
    style: TextStyle(
      color: Colors.white
    )
  )
),
FlatButton(
  onPressed: _resetCounter,
  color: Colors.blue,
  child: Text(
    'リセット',
    style: TextStyle(
      color: Colors.white
    )
  )
)

19.gif

無事に動きました。 関数の中にある setState() は状態を更新してくれる関数のようです。
消した場合、内部的に値は変わってるけど画面に反映されませんでした。

デバッグのためにログを出したいときは?

print() でConsoleに表示することができるようでした。

20.png

コードまとめ

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _decrementCounter() {
    setState(() {
      _counter--;
    });
  }

  void _resetCounter() {
    setState(() {
      _counter = 0;
    });
  }

  @override
  Widget build(BuildContext context) {

    var largeText = TextStyle(
        fontSize: 24,
        fontWeight: FontWeight.bold,
        color: Colors.red
    );

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'これはFlutterのサンプルアプリです',
              style: largeText
            ),
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            FlatButton(
              onPressed: _incrementCounter,
              color: Colors.blue,
              child: Text(
                '足す',
                style: TextStyle(
                  color: Colors.white
                )
              )
            ),
            FlatButton(
              onPressed: _decrementCounter,
              color: Colors.blue,
              child: Text(
                '引く',
                style: TextStyle(
                  color: Colors.white
                )
              )
            ),
            FlatButton(
              onPressed: _resetCounter,
              color: Colors.blue,
              child: Text(
                'リセット',
                style: TextStyle(
                  color: Colors.white
                )
              )
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

まとめ・所感

本当はTodoアプリくらいまで作りたかったのですが、思ったより時間がかかってしまったのと記事も長くなってしまったので、また別で書こうと思います。

まだ、ちょっと触ったくらいで機能なども全然使えてませんが、Widgetを組み合わせて構築していくところに慣れれば、僕のような普段はフロントエンドをやってるエンジニアでも簡単なアプリだったら作れそうな気がしたので、もうちょっと触ってみようと思います。

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?