LoginSignup
14
10

More than 5 years have passed since last update.

[Flutter] Write Your First Flutter App, part 1 (の翻訳)

Last updated at Posted at 2018-08-29

Flutter の codelab part1 の翻訳です。
https://flutter.io/get-started/codelab/

英語力皆無 & Dart未経験 & 初Flutter な自分が書いているので、間違いや理解しづらい点などあるかと思います。変なところがあればご指摘ください :angel:

Write Your First Flutter App, part 1

これからスタートアップ会社の名前の候補を生成するアプリを作ってみます。このアプリはテーブル(List)の各セルに候補となる文字列を表示し、ユーザは表示された候補の中からベストな名前をselectしたりunselectすることができます。コードはユーザのスクロールに合わせて名前を遅延生成します。ユーザがスクロールすれば名前はそれに従って生成され、またどこまでもスクロールすることができます。

startup_namer_1_opt.gif


Part 1 で学ぶこと

  • iOSとAndroidの両方で自然な見栄えのアプリをFlutterで書く方法
  • Flutterアプリの基本的な構造
  • 機能を拡張するためのパッケージ (package)の探し方と使い方
  • より高速な開発のためのホットリロード (hot reload) の利用
  • Stateful widget の作り方
  • 無限かつ遅延的にロードされるリストの作り方

Step 1: Create the starter Flutter app

  1. Replace the contents of lib/main.dart の中身を以下の内容に置き換えてください。もともとあったコードはすべて削除し、以下のコードを貼り付けてください。このコードは画面の中央に「Hello World」と表示させるものです。
import 'package:flutter/material.dart';

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

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: Text('Hello World'),
        ),
      ),
    );
  }
}
  1. IDEの緑色の矢印ボタンを押して実行すると、AndroidやiOSに出力されます。

iPhoneXだとこうなります。

HelloWorld_iPhoneX.png

Androidだとこう。

HelloWorld_Android.png

Observation

  • この example では Material app を作っています。 Material とはモバイルやWebで標準的な visual design languageです。Flutter はリッチな Material Widget を提供します。
  • main メソッドは => (太矢印, fat arrow) を利用しています。 => 表記は1行の関数やメソッドを記述するために使います。
  • MyAppStatelessWidget を継承していてそれ自体が widget です。 Flutter の世界では、ほぼすべてが widget です。 alignment, padding, layout なども widget です。
  • Material Library に所属する Scaffold widget は app bar, title, ホーム画面用の widget ツリーを保持する body プロパティを提供します。 widget のツリーはかなり複雑になりえます。
  • widget の主な役割は build() メソッドを提供することです。 これは、下位レベルの widget を用いてその widget をどのように描画するべきなのかを記述するものです。
  • 今回の例では body は Text widget を child として保持する Center widget で成り立っています。 Center widget はそのサブツリーを画面の中央に配置します。

Step 2: Use an external package

ここでは、 english_words という open-source パッケージを使ってみましょう。このパッケージは数千よく使用される英単語といくつかの便利な関数を提供します。

english_words パッケージは他の多くのパッケージと同様に、 pub.dartlang.org で見つけることができます。

find_package.png

  1. pubspec ファイルは Flutter アプリにおいて asset と 依存するパッケージなどを管理するものです。 pubspec.yaml の依存パッケージリストに english_words (3.1.0以上) を追加しましょう。

    dependencies:
      flutter:
        sdk: flutter
    
      cupertino_icons: ^0.1.0
      english_words: ^3.1.0
    
    
  2. IDE の Editor View にて、 Packages Get をクリックします。これはパッケージをプロジェクトに引っ張ってきます。Console画面にこんな出力がされます。

    > flutter packages get
    Running "flutter packages get" in `YOUR_APP_NAME`...
    Process finished with exit code 0
    

    これが実行されると、pubspec.lock が自動生成されプロジェクトに引っ張ってきたすべてのパッケージ名とバージョン番号が記述されます。

  3. lib/main.dart に新しいパッケージの import を記述します。

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

    impor... と入力していくと IDE はインポートするパッケージを補完してくれるでしょう。入力を完了すると、この1行がグレーアウトされます。これは(今の所)インポートしたパッケージが使用されていないことを示しています。

  4. "Hello World" の代わりに english_words パッケージで生成した単語を表示してみましょう。
    以下のように先程の lib/main.dart の中身を書き換えます。

    import 'package:flutter/material.dart';
    import 'package:english_words/english_words.dart';
    
    void main() => runApp(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('Hello World'), // ★コメントアウト
              child: Text(wordPair.asPascalCase),  // ★追加
            ),
          ),
        );
      }
    }
    

    ここで「Pascal case」というのは UpperCamelCase のことです。C++やSwiftなどの多くの言語におけるクラス名の命名法と同じ形式です。

  5. 今、アプリが動いている状態であれば、CMD+S (上書き保存)もしくは雷マークをクリックすることで hot reload が発動します。Hot reload を実行するたびに、ホーム画面の中央に違う名前がランダムに表示されます。なぜ毎回違う名前が表示されるかというと、 wordPairbuild() の中で生成されており、 build()MaterialApp が描画を要求するたび、または、Flutter Inspector で Platform を切り替えるたびに呼ばれるからです。

Step3: Add a Stateful widget

State*less* widgets (状態なしwidget) は immutable, つまり、すべてのプロパティは final で、値の変更はできません。

State*ful* widgets (状態ありwidget)はそれが生きている限り、状態(プロパティ)が維持・変更されます。Stateful widget を実装するためには、少なくとも2つのクラスが必要です。

  1. StatefulWidget
  2. State

StatefulWidget class が State class を作成します。StatefulWidget class は、それ自体は immutable つまり値変更できません、しかし State class は widget のが生きている限り存続します。

このステップでは、 RandomWords という stateful widget を追加します。このクラスは、その State である RandomWordsState クラスを生成します。そして RandomWords widget をすでにある MyApp stateless widget の中に child として追加します。

  1. まず最小限の state class を作成します。以下を main.dart の最後に追加してください。

    class RandomWordsState extends State<RandomWords> {
      // TODO Add build method
    }
    

    State<RandomWords> という宣言に着目してください。これは、 RandomWords class が使用することに特化した generic State class を使用する、ということを意味します。App の状態の殆どはここに置かれます。ここに RandomWords widget のための state が保持されます。この class はユーザのスクロール操作によって無限的に生成される word pair を保存します。また、 ユーザがハートアイコンをタップすることでトグルできるお気に入りの状態(これは Part 2 で!)も保存します。

    RandomWordsStateRandomWords class に依存します。それを追加しましょう。

  2. 状態を持つ RandomWords widget を main.dart に追加します。RandomWords widget はその State class を作成すること以外、ほとんど何もしません。

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

    この state class を追加すると、IDE は build method が足りない、と怒ります。なので次の段階で、基本的な word pair を生成する処理を build method として記述します。生成するコードを MyApp から RandomWordsState に移動させます。

  3. RandomWordsStatebuild() method を追加します。

    class RandomWordsState extends State<RandomWords> {
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random();
        return Text(wordPair.asPascalCase);
      }
    }
    
  4. 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(), // ★ こう変える
            ),
          ),
        );
      }
    }
    
  5. App をリスタート(緑の再生ボタンやCMD+Sキーで)させます。変更前と挙動は同じになるはずです。つまりHot Reload するたびに違う単語が表示されます。


もし hot reload の際に以下のような警告が出た場合、 App をリスタートさせてみてください。

Reloading…
Some program elements were changed during reload but did not run when the view was reassembled; you may need to restart the app (by pressing “R”) for the changes to have an effect.

これはおそらく偽陽性(つまり本当は問題がない)です。リスタートさせれば変更が UI に反映されていることが確認できるでしょう。


Step 4: Create an infinite scrolling ListView

このステップでは、 RandomWordsState を拡張し、 word pair のリストを生成するようにします。ユーザがスクロールするにしたがって、リストは ListView widget に表示されます。そしてこのリストは無限に増えます。 ListViewbuilder factory constructor を使うことで、list view を要求に応じて遅延的に作ることができます。

  1. 候補として生成した単語群を保存するために、 RandomWordsState class に _suggestions を追加します。また、フォントサイズをより大きくするために、 biggerFont 変数も追加します。

    Dart言語では、アンダースコア _ をつけることで、library 内からのみ見えるようにできます。

    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
    
      final _biggerFont = const TextStyle(fontSize: 18.0);
      ...
    }
    

    次は、 RandomWordsState_buildSuggestions() を追加します。このメソッドは候補となる単語を表示する ListView を作成します。

    ListView class は itemBuilder という builder property を提供します。ここには匿名関数を渡します。渡した関数は BuildContext と 行番号 i が渡されます。この行番号は 0 から始まり、1ずつインクリメントされます。これに応じて単語を生成します。この方式を採用することで、ユーザのスクロール操作に応じて無限に単語を提示するリストを作ることができます。

  2. 以下の _buildSugge() 関数を RandomWordsState class に追加します。(コメントは消しても良いです。)

    class RandomWordsState extends State<RandomWords> {
      ...
      Widget _buildSuggestions() {
        return ListView.builder(
          padding: const EdgeInsets.all(16.0),
          // itemBuilder は単語ごとに1回呼び出され、その単語を1つの ListTile として配置します。
          // 偶数行なら、この関数は ListTile を生成し、
          // 奇数業なら、この関数は Divider widget (区切り線)を生成します。
          // ただし、小さなデバイスではこの線は見えづらいかもしれません。
          itemBuilder: (context, i) {
            // 1 pixel の区切り線を各行の前に表示します。
            if (i.isOdd) return Divider();
    
            // "i ~/ 2" という文法は、 i を 2で割って整数を返します。
            // 例えば、 i が 1, 2, 3, 4, 5 なら結果は 0, 1, 1, 2, 2 となります。
            // これは ListView 内の実際の単語数を計算します。つまり区切り線の数を引きます。
            final index = i ~/ 2;
            // もし利用可能な単語群の最後に到達したら...
            if (index >= _suggestions.length) {
              // ...最後まで来たら、更に10単語生成し、suggestions list に追加します。
              _suggestions.addAll(generateWordPairs().take(10));
            }
            return _buildRow(_suggestions[index]);
          }
        );
      }
    }
    

    _buildSuggestions() 関数は _buildRow() を1単語ごとに呼びます。この関数は単語を ListTile に配置します。 ListTile は次のステップでリストの row (行) をより魅力的にしてくれます。

  3. _buildRow() メソッドを RandomRowrdsState に追加します。

    class RandomWordsState extends State<RandomWords> {
      ...
    
      Widget _buildRow(WordPair pair) {
        return ListTile(
          title: Text(
            pair.asPascalCase,
            style: _biggerFont,
          ),
        );
      }
    }
    
  4. RandomWordsStatebuild() メソッドを _buildSuggestions() を使うように編集します。単語生成ライブラリを直接呼ばないようにします。(Scaffold は Material Design の基本的なレイアウトを提供します。)

    class RandomWordsState extends State<RandomWords> {
      ...
      @override
      Widget build(BuildContext context) {
        // final wordPair = WordPair.random(); // ★削除
        // return Text(wordPair.asPascalCase); // ★削除
        return Scaffold (
          appBar: AppBar(
            title: Text('Startup Name Generator'),
          ),
          body: _buildSuggestions(),
        );
      }
      ...
    }
    
  5. MyApp の build method も更新しましょう。home を RandomWords widget にするだけです。

    もともとの記述を以下のように変更します。

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Startup Name Generator',
          home: RandomWords(),
        );
      }
    }
    
  6. App をリスタートさせましょう。どれだけスクロールしても単語がリストに表示されることが確認できるでしょう。

iOS。

part1_complete_ios.png

Android

part1_complete_android.png

14
10
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
14
10