Flutterは、GoogleのUIツールキットです。これは単一のコードベースからモバイル、ウェブ、デスクトップ向けのネイティブにコンパイルされた美しいアプリケーションを構築するためのツールです。Flutterは既存のコードと一緒に動作し、世界中の開発者や組織で使用されている、無料でオープンソースのプロダクトです。

このコードラボでは、シンプルなモバイルFlutterアプリを作成します。オブジェクト指向のコードや、変数、ループ、条件分岐などの基本的なプログラミングの概念に精通していれば、このコードラボを修了することができます。Dartやモバイル、Webプログラミングの経験は必要ありません。

パート1で学べること

このコードラボのパート2では、インタラクティブな機能を追加し、アプリのテーマを変更し、新しいページ(Flutterではルートと呼ばれます)にナビゲートする機能を追加します。

パート1で作るもの

スタートアップ企業の名前案を生成するシンプルなアプリを実装します。ユーザーは名前を選択したり解除したりすることができ、最適なものを保存します。コードでは、一度に10個の名前を遅延生成します。ユーザーがスクロールすると、より多くの名前が生成されます。ユーザーがスクロールできる範囲に制限はありません。

次のアニメーションGIFは、パートの完了時にアプリがどのように動作するかを示しています。

6556f8b61acd6a89.gif

このラボを完成させるには、Flutter SDKエディターの2つのソフトウェアが必要です。(このコードラボでは、Android Studio を使用していることを前提としていますが、お好みのエディタを使用することができます)。

コードラボを実行するには、次のいずれかのデバイスを使用します。

a3c16fc17be25f6c.pngシンプルでテンプレート化されたFlutterアプリを作成します。startup_namerという名前のFlutterプロジェクトを作成し、以下のコマンドで null safety に移行させます。

$ flutter create startup_namer
$ cd startup_namer

ここでやることは、Dartのコードが格納されているlib/main.dartの編集です。

a3c16fc17be25f6c.pnglib/main.dartからすべてのコードを削除し、lib/main.dartの内容を以下のコードに置き換えます。これは、画面の中央に「Hello World」を表示します。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

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

a3c16fc17be25f6c.pngアプリを実行します。お使いのデバイスに応じて、Android、iOS、Webのいずれかの出力が表示されるはずです。

71dd22da186608e5.png 考察

このステップでは、english_wordsというオープンソースのパッケージを使い始めます。このパッケージには、最もよく使われる数千の英単語と、いくつかの便利な関数が含まれています。

english_wordsパッケージは、他の多くのオープンソースパッケージと同様に、pub.devで見つけることができます。

a3c16fc17be25f6c.png english_words パッケージをこのアプリの依存関係に追加します。

$ flutter pub add english_words
Resolving dependencies...
  async 2.8.1 (2.8.2 available)
  characters 1.1.0 (1.2.0 available)
+ english_words 4.0.0
  matcher 0.12.10 (0.12.11 available)
  test_api 0.4.2 (0.4.5 available)
  vector_math 2.1.0 (2.1.1 available)
Changed 1 dependency!

a3c16fc17be25f6c.png lib/main.dartで、新しいパッケージをインポートします。

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';  // Add this line.

文字を入力すると、Android Studioはインポートするライブラリの候補を表示します。そして、グレーで表示されたインポート文字列は、インポートされたライブラリが(今のところ)未使用であることを教えてくれます。

次に、"Hello World "文字列を使う代わりに、english_wordsパッケージを使ってテキストを生成します。

a3c16fc17be25f6c.png 以下のように変更します。

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final wordPair = WordPair.random(); // Add this line.
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Welcome to Flutter'),
        ),
        body: Center(                          // Drop the const, and
          //child: Text('Hello World'),        // Replace this text...
          child: Text(wordPair.asPascalCase),  // With this text.
        ),
      ),
    );
  }
}

a3c16fc17be25f6c.png アプリが実行中の場合は、ホットリロードで実行中のアプリを更新します。(コマンドラインから r を入力するとホットリロードされます。) ホットリロードをクリックするか、プロジェクトを保存するたびに、実行中のアプリにランダムに選ばれた異なる単語ペアが表示されるはずです。これは単語のペアがビルドメソッド内で生成され、MaterialAppがレンダリングを要求するたびに、またはFlutter Inspectorでプラットフォームを切り替えるときに実行されるからです。

問題がありましたか?

アプリが正常に動作しない場合は、誤字脱字を確認してください。必要に応じて、以下のリンクのコードを使って元に戻してください。

pubspec.yaml

lib/main.dart

StatelessWidget は不変オブジェクトです。つまり、そのプロパティは変更できず、すべての値は最終的なものです。

StatefulWidget は、変更される可能性のある状態をウィジェットに保持します。StatefulWidgetを実装するには、少なくとも以下の2つのクラスが必要です:

1) StatefulWidget クラス (Stateクラスのインスタンスを生成します)

2) State クラス

StatefulWidgetオブジェクトは、それ自体は不変で、破棄したり再生成したりすることができますが、Stateオブジェクトはウィジェットのライフタイム(生存期間中)にわたって状態を維持します。

このステップでは、RandomWordsというステートフルなウィジェットを追加し、そのStateクラスである_RandomWordsStateを定義します。そして、既存のMyApp StatelessWidget の子ウィジェットとしてRandomWordsを使用します。

a3c16fc17be25f6c.png StatefulWidgetのボイラープレート・コード(定型的なコード)を作成します。

このコードは、MyApp以外のファイルのどこに置いても構いませんが、この例では lib/main.dartファイルの一番下に配置しています。lib/main.dartファイルで、すべてのコードの後にカーソルを置き、Returnを数回入力して新しい行を追加します。IDEでstfulと入力すると、エディタが StatefulWidget クラスを作成するかどうかを尋ねてきます。Returnキーを押して承諾すると、2つのクラスの定型的なコードが表示され、 StatefulWidgetの名前を入力するためのカーソルが配置されます。

a3c16fc17be25f6c.png ウィジェットの名前に RandomWords と入力します。

以下のコードにあるように、RandomWordsウィジェットは、Stateクラスを作成する以外にはほとんど何もしません。

ステートフルなウィジェットの名前としてRandomWordsを入力すると、IDEは自動的に付属のStateクラスを更新し、_RandomWordsStateと命名します。デフォルトでは、Stateクラスの名前の前にアンダースコアが付いています。識別子の前にアンダースコアを付けることで、Dart言語ではプライベート宣言(非公開)となり、Stateオブジェクトのおすすめ実装として推奨されています。

また、IDEはStateクラスを自動的に更新して、State<RandomWords> を継承させます。これは、RandomWordsでの使用に特化した汎用のStateクラスを使用していることを示しています。アプリのロジックのほとんどがここにあり、RandomWordsウィジェットの状態を維持しています。このクラスは、生成された単語のペアのリストを保存します。このリストは、ユーザーがスクロールするたびに無限に増えていきます。また、このラボのパート2では、ユーザーがハートのアイコンの ON/OFF 切り替えすることで、リストに追加したり削除したりして、お気に入りの単語ペアを保存します。

両クラスは以下のようになりました。

class RandomWords extends StatefulWidget {
  const RandomWords({super.key});

  @override
  State<RandomWords> createState() => _RandomWordsState();
}

class _RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

a3c16fc17be25f6c.png _RandomWordsStateのbuild()メソッドを更新します。

_RandomWordsStateのbuild()メソッドの中の return Container(); を以下の2行に置き換えます。

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

a3c16fc17be25f6c.png 以下のように変更して、MyAppから単語生成コードを削除します。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final wordPair = WordPair.random();  // DELETE

    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: const Center(                     // Add the const
          //child: Text(wordPair.asPascalCase), // REPLACE with...
          child: RandomWords(),                 // ...this line
        ),
      ),
    );
  }
}

a3c16fc17be25f6c.png アプリをホットリロードします。アプリは以前のように動作し、アプリをホットリロードまたは保存するたびに単語のペアが表示されます。

問題がありましたか?

アプリが正常に動作していない場合は、以下のリンクのコードを使って元に戻すことができます。

このステップでは、_RandomWordsStateを拡張して、単語のペアのリストを生成して表示します。ユーザーがスクロールすると、(ListViewウィジェットに表示される)リストは無限に増えていきます。ListViewの builder ファクトリ・コンストラクタを使うと、必要に応じてスクロールされるときにリストビューを随時生成することができます。

a3c16fc17be25f6c.png _RandomWordsStateクラスにいくつかの状態変数を追加します。

提案された単語のペアを保存するための_suggestionsリストを追加します。また、フォントサイズを大きくするための_biggerFont変数を追加しましょう。

class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];                 // NEW
  final _biggerFont = const TextStyle(fontSize: 18); // NEW
  ...
}

次に、_RandomWordsStateクラスに_buildSuggestions()関数を追加します。このメソッドは、提案された単語のペアを表示するListViewを構築します。

ListViewクラスには、ビルダープロパティであるitemBuilderがあり、これはファクトリービルダーとコールバック関数を匿名関数として指定したものです。BuildContextと行番号 i という、2つのパラメーターが関数に渡されます。イテレータは0から始まり、関数が呼ばれるたびに増加し、提案された単語のペアごとに1つ増加します。このモデルでは、ユーザーがスクロールするたびに提案リストが増え続けます。

a3c16fc17be25f6c.png _RandomWordsState のビルドメソッドを更新します。

word-generation libraryを直接呼び出すのではなく、_buildSuggestions()を使用するように変更する。

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      padding: const EdgeInsets.all(16.0),
      itemBuilder: (context, i) {
        if (i.isOdd) return const Divider();

        final index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return ListTile(
          title: Text(
            _suggestions[index].asPascalCase,
            style: _biggerFont,
          ),
        );
      },
    );
  }

itemBuilderコールバックは、単語の組み合わせの提案ごとに1回呼び出され、各提案をListTileの行に配置します。偶数行の場合、この関数は、単語の組み合わせのための ListTile 行を追加します。奇数列の場合は、エントリを視覚的に分離するための Divider ウィジェットが追加されます。なお、小さな端末では、この区切り線が見にくい場合があります。

a3c16fc17be25f6c.png MyAppのビルドメソッドを更新し、タイトルを2か所変更します。

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Startup Name Generator'),
        ),
        body: const Center(
          child: RandomWords(),
        ),
      ),
    );
  }

a3c16fc17be25f6c.png アプリを再起動します。どこまでスクロールしても、単語ペアのリストが表示されるはずです。

問題がありましたか?

アプリが正常に動作しない場合は、以下のリンクのコードを使って元に戻すことができます。

おめでとうございます!

このコードラボのパート1が完了しました! このアプリを拡張したい場合は、パート2に進んで、以下のようにアプリを修正してください。

パート2が完了すると、アプリは以下のようになります。

7fcab088cd22cff7.gif

その他の次のステップ

Flutter SDKの詳細については、以下のリソースをご参照ください。

その他、以下のような資料があります。

また、Flutterコミュニティにもご参加ください。