💻

[Flutter入門(2)] Write your first Flutter app, part 1をやってみる

2020/06/24に公開

Flutterの公式サイトに、チュートリアルよりも前にやってみる用のコンテンツとして Write your first Flutter app, part 1 というものが用意されているので、この内容を簡単なコードの解説を交えながら紹介していきます。

Step 1:アプリを作る

まず、Getting Started with your first Flutter app の手順でサンプルアプリを作ります。このとき、アプリ名は my_app ではなく今回は startup_namer にします。

$ flutter create startup_namer

サンプルアプリができたら、iOSシミュレータを立ち上げて、アプリを起動しましょう。

$ open -a Simulator
$ cd startup_namer
$ flutter run

これでとりあえずサンプルアプリがそのまま動いている状態ですね。

ここまで来たら、 lib/main.dart の中身を以下のコードでごっそり置き換えてしまいましょう。

import 'package:flutter/material.dart';

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

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

コードを変更したら、 flutter run しているターミナルで r キーをタイプすることでホットリロード(起動中のアプリにソースコードの変更内容を即時反映させるFlutterのデバッグ用機能)ができます。

$ flutter run
:
:
:
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on iPhone SE (2nd generation) is available at: http://127.0.0.1:64038/E4xtLgtlcWU=/

# ここで r キーをタイプ

Performing hot reload...
Reloaded 1 of 503 libraries in 164ms.

# 起動中のアプリに変更が反映される

ちなみに、VS CodeなどのIDEにFlutterプラグインを導入して使っている場合は、ソースコードの変更を保存したら即座にホットリロードが実行されるようにすることも可能です。

例えばVS Codeの場合なら、ターミナルで flutter run する代わりに 実行 > デバッグの開始 (または F5 )でアプリを起動すれば、ソースコードを変更して保存したら即座にアプリの状態に反映されます。
VS Code自体のセットアップ方法は こちらの公式ドキュメント をご参照ください。

lib/main.dart を上記の内容に変更すると、アプリの画面は以下のような内容になります。

コードの内容のうちいくつかポイントとなる点を確認しておきましょう。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          child: Text('Hello World'),
        ),
      ),
    );
  }
}
  • MaterialApp クラスを使って、Materialデザイン に準拠したアプリを作成している。FlutterはMaterialデザインを実装した様々なウィジェットを提供している
  • main() メソッドはアロー記法( => )を使っている(JavaScriptのアロー関数と同様、関数の処理が1行の場合はこの省略記法が使える)
  • MyApp 自体が StatelessWidget クラスを継承していて、アプリそれ自体が「ウィジェット」になっている点に注目。Flutterではほとんどすべてのもの(alignment, padding, layoutなども含む)がウィジェットである
  • Scaffold ウィジェットはFlutter標準のMaterialライブラリにより提供されている。このウィジェットは appBar title body プロパティを持っていて、ホームスクリーンの見た目を簡単に作成できる
  • ウィジェットのメインの仕事は build() メソッドを提供することである。 build() メソッドでは、他のウィジェットを組み合わせるなどしてウィジェット全体の見た目を構築する
  • この例では body に「 Text ウィジェットを子として持つような Center ウィジェット」を渡している。 Center ウィジェットは子要素を中央寄せしてくれるウィジェットである

Step 2:外部パッケージを使ってみる

english_words という外部パッケージを導入してみます。

pubspec.yaml というファイルに以下のような1行を追記します。

  dependencies:
    flutter:
      sdk: flutter
    cupertino_icons: ^0.1.2
+   english_words: ^3.1.5

追記したら、アプリのディレクトリ直下( flutter run しているのと同じ階層)で以下のコマンドを実行してパッケージをインストールします。

$ flutter pub get

VS CodeにFlutterプラグインを入れて使っている場合は、 pubspec.yaml の変更を保存したときに自動で flutter pub get が実行されるので、自分で別途実行する必要はありません✋

次に、 lib/main.dart でこのパッケージを利用するために、ファイルの先頭に以下の import 文を追記します。

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

最後に、このパッケージを使用して画面に出力する文字を Hello World ではなくランダムな単語の組み合わせにしてみます。

  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
+     final wordPair = WordPair.random();
      return MaterialApp(
        title: 'Welcome to Flutter',
        home: Scaffold(
          appBar: AppBar(
            title: Text('Welcome to Flutter'),
          ),
          body: Center(
+           child: Text('Hello World'),
+           child: Text(wordPair.asPascalCase),
          ),
        ),
      );
    }
  }

下図のように、 Hello World の代わりにランダムな単語を並べた文字列が表示されるようになりました。

ホットリロードする度に表示される文字列が変わるはずです。試してみてください。

上手くホットリロードされない場合は、一度 flutter run を終了(ターミナル上で Ctrl + C で終了できます)させて、再度 flutter run してアプリを再起動してみてください。

VS Codeの場合なら 実行 > デバッグの再起動 で再起動できます。

Step 3:Statefulウィジェットを作ってみる

Flutterには Stateless ウィジェットと Stateful ウィジェットがあります。

Statelessウィジェットは状態が変化しない(イミュータブルな)ウィジェットです。

反対にStatefulウィジェットは状態が変化しうる(ミュータブルな)ウィジェットです。ウィジェットが生まれてから破棄されるまでの間に、保持している値が変化する可能性があります。

Statefulウィジェットを実装するためには StatefulWidgetState という2つのクラスが必要になります。

  • StatefulWidget クラスが State クラスのインスタンスを作る
  • StatefulWidget クラスそのものはイミュータブルだが
  • State クラスが対応するウィジェットの状態を保持する

という関係です。

ここでは、 RandomWords というStatefulウィジェットを作ってみます。 RandomWords ウィジェットは _RandomWordsState というStateクラスを作成します。最終的には、 MyApp Statelessウィジェットの子要素として RandomWords ウィジェットを持たせる形になります。

まず、 lib/main.dart の末尾に以下のコードを追記して、2つのクラスを定義します。

class RandomWords extends StatefulWidget {
  
  _RandomWordsState createState() => _RandomWordsState();
}

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

_RandomWordsState クラスのクラス名の先頭の _ は、Dart言語における private アクセス修飾子です。名前の先頭に _ をつけることでこのクラスは private になります。

また、 _RandomWordsState クラスが State<RandomWords> を継承している点にも注目です。「 RandomWords クラス用として使われる State クラスである」ということを明示しています。

最後に、こうして定義した RandomWords ウィジェットを、 MyApp クラスで使うようにコードを修正します。

  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
-     final wordPair = WordPair.random();
      return MaterialApp(
        title: 'Welcome to Flutter',
        home: Scaffold(
          appBar: AppBar(
            title: Text('Welcome to Flutter'),
          ),
          body: Center(
-           child: Text(wordPair.asPascalCase),
+           child: RandomWords(),
          ),
        ),
      );
    }
  }

アプリを再起動するなどしても、先ほどまでと同じような動作(ホットリロードする度に毎回ランダムな文字列が表示される)になっていればOKです👍

Step 4: 無限スクロールできるListViewを作ってみる

_RandomWordsState を、ランダムな単語ペアの リスト を生成するようなクラスに改造してみましょう。

ListView ウィジェットを使い、その builder() ファクトリが遅延的に実行される性質を生かして、無限にスクロールできるものにしていきます。

まず、 _RandomWordsState クラスに以下の2つの final (変更不可) なクラス変数を追加します。

  class _RandomWordsState extends State<RandomWords> {
+   final _suggestions = <WordPair>[];
+   final _biggerFont = TextStyle(fontSize: 18.0);
+
    @override
    Widget build(BuildContext context) {
      final wordPair = WordPair.random();
      return Text(wordPair.asPascalCase);
    }

_suggestions は提示する単語ペアのリスト( ListView ではなく情報としてのリスト)を保持する変数、 _biggerFont はあとでテキストのフォントサイズを大きくするときに使うための単なるスタイルの定義です。

次に、同じく _RandomWordsState クラスに、 ListView ウィジェットを組み立てるための _buildSuggestions() メソッドを作ります。

Widget _buildSuggestions() {
  return ListView.builder(
    padding: EdgeInsets.all(16.0),
    itemBuilder: /*1*/ (context, i) {
      if (i.isOdd) return Divider(); /*2*/

      final index = i ~/ 2; /*3*/
      if (index >= _suggestions.length) {
        _suggestions.addAll(generateWordPairs().take(10)); /*4*/
      }
      return _buildRow(_suggestions[index]);
    }
  );
}

コードの解説は以下のとおりです。

  • /*1*/itemBuilder に渡すコールバックは、各単語ペアごとに呼び出されて、その単語ペアを _buildRow() メソッド(まだこの時点では定義していない)に渡すことで ListTile ウィジェット(リストの1行を表すウィジェット)を生成する。ただし、偶数行目では ListTile を返すが、奇数行目では何もせず Divider ウィジェットを 返す点に注意
  • /*2*/Divider ウィジェットで高さ1ピクセルの仕切り線を作成(各行の間に仕切りを入れて見やすくするためだけ)
  • /*3*/i ~/ 2 という式は、「2で割った結果を小数点以下切り捨てて整数にする」という演算をする。例えば、 1, 2, 3, 4, 5 が、この演算を通すことで 0, 1, 1, 2, 2 になる。この計算により、仕切り線の数を無視した実際の要素の数を算出している
  • /*4*/ :生成済みの単語ペアをすべて消費した場合に、さらに10個の単語ペアを追加で生成してリスト( ListView ではなく情報としてのリスト)に追加している

さらに、先ほどの _buildSuggestions() メソッドから単語ペアを受け取ってリストの1行を ListTile ウィジェットとして生成する _buildRow() メソッドを実装します。

Widget _buildRow(WordPair pair) {
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
  );
}

これでパーツは整ったので、実際に _RandomWordsState クラスの build() メソッドでこれらの処理を実行して ListView を作れるようにします。

  @override
  Widget build(BuildContext context) {
-   final wordPair = WordPair.random();
-   return Text(wordPair.asPascalCase);
+   return Scaffold(
+     appBar: AppBar(
+       title: Text('Startup Name Generator'),
+     ),
+     body: _buildSuggestions(),
+   );
  }

最後に、 MyApp ウィジェット自体の build() メソッドも更新して、 RandomWords ウィジェットをそのまま表示するようにすれば完了です。

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

実行すると下図のような感じで表示されます。

マウスでドラッグすればスクロールできるので、無限にスクロールできることを確認してみてください👍

GitHubで編集を提案

Discussion