Chapter 03

最初のアプリ

takumma
takumma
2021.09.13に更新

このチャプターでは最初に作成したアプリのコードを参考に、 Flutter アプリがどのように作成されているかを見ていきます。

最初のアプリ作成

まずは最初のアプリを作成しましょう!
flutter アプリの作成はコマンドでおこなう方法と Android Studio でおこなう方法の2つを説明します。どちらの方法で作成していただいてもかまいません。

コマンドで作成する

アプリを作成するコマンドは以下の通りです。
適当な作業ディレクトリに移動してから、コマンドを実行しましょう。

$ flutter create flutter_hands_on

flutter_hands_on はアプリ名です。自由に変更してもかまいませんが、ハイフン - が使えないことに注意してください。

作成出来たら、 Android Studio で開いて見ましょう。
Android Studio を開いて、「Open an Existing Project」で先ほど作成したアプリのディレクトリを選択して OK を押しましょう。

Android Studio で作成する

Android Studio を起動して、「Create New Flutter Project」をクリックします。
Flutter Application」を選択して「Next」をクリックします。

アプリ名などを入力していきましょう。

  • Project Name : アプリ名です。自由に変更してもかまいません。
  • Flutter SDK : 環境構築のチャプターで設定した Flutter を設定します。 bin フォルダではなく、1 つ上の flutter フォルダまでのパスを入力することに注意してください。
  • Project location : プロジェクトを配置するディレクトリを指定してください。
  • Description : アプリの説明を書く欄ですが、飛ばしてもかまいません。

入力を確認して「Next」をクリックします。

パッケージ名などを指定しますが、今回はデフォルトのままで大丈夫です。

「finish」をクリックするとアプリの作成が開始されます。
作成が完了すると、作成したプロジェクトが開かれます。

作成ができたらデバイスの準備をして実行してみましょう。

デバイスの準備

まずは実行する端末を USB で接続したり、エミュレータを起動したりしてください。
実行できるデバイスがあるかどうかは、以下のコマンドで確認ができます。

$ flutter devices

また、Android Studio なら以下の部分をみることで実行できる端末があるかを確認したり、エミュレータを起動したりできます。Xcode が入っていれば、ここから IOS のシミュレータも起動できます。

実行する

実行できるデバイスが確認できたら、早速実行しましょう。
コマンドと Android Studio で実行する方法を説明します。

コマンドで実行

以下のコマンドで実行ができます。

$ cd flutter_hands_on # 作成したアプリのディレクトリに移動
$ flutter run # 実行

Android Studioで実行

プロジェクトを開いた状態で右上の「▷」をクリックすると、実行ができます。

実行したアプリの右下の+ボタンを押すとカウントされます。

アプリを停止したい場合は、右上の赤い「□」をクリックして停止できます。

最初のアプリを読む

まずはコードを書かずに、作成したアプリのソースコードをざっくりと読みながら flutter を学習していきましょう。ここで全て理解する必要はないので、「よくわからないなあ」と思ったらとりあえず飛ばして読むのもいいでしょう。

アプリのディレクトリ内にはさまざまなファイルやフォルダがありますが、 Flutter アプリのソースコード自体は lib/ フォルダの中にあります。
そしてその中の main.dart を開いてください。アプリを作成した段階では、アプリのソースコードは実質この main.dart だけです。
では、この main.dart の中身を詳しく見ていきましょう。

一応ここにも載せておきます。

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

一番上から読んでいきましょう。

インポート文

import 'package:flutter/material.dart';
  • マテリアルデザインの UI コンポーネントを使うためにパッケージをインポートしています。

main関数

void main() {
  runApp(const MyApp());
}
  • アプリを実行すると、この main 関数が実行されます。
  • この main 関数の中の runApp() という関数を実行してアプリを実行しています。
  • 関数の引数(ここでは MyApp())には、実行するアプリを構成するWidgetウィジェット)が渡されます。

MyAppクラス

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
  • runApp() の引数として渡されていた MyApp クラスで、 StatelessWidget を継承しています。
  • MaterialAppWidget を返していますが、マテリアルデザインを使用する Flutter アプリでは基本的にこの MaterialAppWidget でアプリ全体を覆います。
  • themeでアプリのカラーテーマを、homeでアプリを起動したとき最初に表示するページを指定します。

MyHomePageクラス・_MyHomePageStateクラス

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

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}


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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
  • MyHomePageStatefulWidget を継承しています。
  • _MyHomePageStateState<MyHomePage>を継承しています。

MyHomePage

  • createStateメゾットで _MyHomePageState を返しています。
  • 引数付きのコンストラクタを用意して、titleに値受け取っています。
  • 今回の場合は MyApp で Flutter Demo Home Page という値を渡しているので title にはその文字が入ります。State 内では titlewidget.title というようにして参照できます。
class MyHomePage extends StatefulWidget {
  // コンストラクタ
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}
home: const MyHomePage(title: 'Flutter Demo Home Page'),

MyHomePageState

状態の保持と更新

  • State 内では _counter でカウントする数を保持し、_incrementCounterメゾットで _counter の数を増やしています。
  • _counter++;を覆っている setState() というメゾットがあります。これは値が変更されたことを通知して UI を再描画するためのメゾットです。このメゾットで包まないと、値は変わりますが実際の画面に表示される数は変わりません。つまり、状態が変わっただけでその変更が UI に伝わってないということです。
int _counter = 0;

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

Scaffold

  • build メゾットで返している Widget ツリーの根元は Scaffold になっています。

  • Scaffoldappbarbody などを指定してページ全体を作成する Widget です。

  • マテリアルデザインのアプリを作る場合はページ全体をこの Scaffold で覆うのが基本です。MaterialAppはアプリ全体、Scaffoldはページ全体を覆うということを覚えておきましょう。

  • Scaffoldで指定している appbar は画面上部のバー(Flutter Demo Home Page と書かれている)です。

  • body はページの中身の Widget ツリーを指定しています。

  • floatingActionButtonは画面右下のボタンです。


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(),
    body: Center(),
    floatingActionButton: FloatingActionButton(),
  );
}

appbar

  • title で appbar に表示するタイトルの文字を指定しています。
  • widget.titleで引数として受け取った値を参照しています。
appBar: AppBar(
  title: Text(widget.title),
),

body

  • body の Widget ツリーを構成する各 Widget の説明は以下のようになっており、これらの Widget を childchildren を使って親子関係を作ることで Widget ツリーを構成しています。
    • Center: は子の要素を中央寄せにする Widget。
    • Column: childrenで Widget の配列を指定し、その Widget を縦に並べる Widget。
    • Text: 文字を表示する Widget。
  • Text 内の $_counter でカウントする数を表示しています。
body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      const Text(
        'You have pushed the button this many times:',
      ),
      Text(
        '$_counter',
        style: Theme.of(context).textTheme.headline4,
      ),
    ],
  ),
),

floatingActionButton

  • floatingActionButton の中の onPressed_incrementCounter メゾットを指定されています。
  • これにより、タップすると _incrementCounter メゾットが呼び出されてカウントする数が増え、画面に表示される数が変わります。
floatingActionButton: FloatingActionButton(
  onPressed: _incrementCounter,
  tooltip: 'Increment',
  child: const Icon(Icons.add),
),

これでこのチャプターは以上です。

次のチャプターからはこのアプリに手を加えながら TODO アプリを作っていきましょう!