LoginSignup
46
41

More than 5 years have passed since last update.

Flutter入門 ~ First App ~

Last updated at Posted at 2018-05-07

はじめに

最近,僕の周りで話題のFlutterを触ってみました。
Write Your First Flutter Appの内容をそのままやっていきます。

前提

  • Dart, Flutterはインストール済
  • 開発環境は作成済(IntelliJを使用)
  • iOSで実行を確認

Step1: Hello World

とりあえず,Flutterでアプリ作成します。

$ flutter create first_app

このコマンドでFlutterのアプリが作成されます。
変更するのは,基本lib/main.dartファイル。
lib/main.dartファイルを以下の内容で書き換えます。

import 'package:flutter/material.dart';

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

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

よくわからないけど,とりあえずRun。

mainメソッドの=>は1行で関数を書くときに使える記法。MyAppインスタンスを渡してアプリを実行してるっぽいです。
たぶん,Javaとか読める人はなんとなくやってることわかる気がする。。
全体のViewをStatelessWidgetが司っていて,Viewをbuildしてます。MaterialAppがtitle, home, bodyプロパティを持っていてそれぞれに任意のview widgetを渡すことで画面を描画しているようです。

Dart,始めてでもなかなか読みやすいですね。

Step2: 外部のパッケージを使う

パッケージを導入してみます。FlutterのパッケージはFlutter Packagesで探せます。ここでは,english_wordsを導入しています。
Flutterではパッケージをpubspec.yamlファイルで管理します。

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.0
  english_words: ^3.1.0

一番下にenglish_wordsを追加。
追加するとIntelliJの上の方にPackages getというのがでるのでそれを押せばpackageが導入できます。
lib/main.dartを以下のように書き換えます。

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

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

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

これで,bodyに表示される文字列が変わるリロードする度に変わります。
非常に簡単ですね。

Step3: Stateful Widgetを追加する

Stateless Widgetはその名の通り,動きがないウィジェットです。
Stateful Widgetのライフタイムの中で変化する状態を保持します。Stateful Widgetは少なくとも2つのクラスを使用します。

  1. Stateクラス
  2. StatefulWidgetクラス

StatefulWidgetクラスはStateクラスのインスタンスを生成するクラスで,それ自体では変化することはないですが,StateクラスはWidgetのライフタイムを保持しています。
ここでは,RandomWordsStateクラスを生成するRandomWordsというStatefulクラスを追加してみます。RandomWordsStateクラスは,Widgetに表示されたお気に入りワードのペアを保持します。

まず,stateful RandomWordsウィジェットをmain.dartに追加します。
MyAppクラスの外に記述することに注意!

class RandomWords extends StatefulWidget {
  @override
  State<StatefulWidget> createState();
}

次に,RandomWordsStateクラスを追加します。全てのアプリのコードは,RandomWordsウィジェットの状態を保持するこのクラスに属します。このクラスが具体的にすることは,

  • ユーザのスクロールによって生成されるワードのペアを保持する
  • ユーザがハートアイコンをトグルすることでリストに追加/削除されるお気に入りワードのペアを保持する

の2つです。

そして,このクラスを追加すると,buildメソッドがないよと怒られるので,buildメソッドを追加します。このbuildメソッドでは,ワードペアの生成を行います。
MyAppクラスからRandomWordsStateクラスへワード生成のコードを移しておきます。buildメソッドは以下のようになります。

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

MyAppの方のワード生成のコードは削除しておきます。

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

ここまでで一旦実行すると,Step2と同じ用にリロードする度に中央のワードが変更されるという動作を確認できると思います。

Step4: 無限Scroll Viewを作成する

このステップでは,RandomWordsStateクラスを拡張して,ワードのリストを生成して表示します。
ListViewのbuilderファクトリーコンストラクタは,要求に応じてリストビューを作成します。

まず,提案されたワードを保持する_suggestionsリストをRandomWordsStateクラスに追加します。
ちなみに,変数の頭のアンダースコア(_)は,Dartにおいてプライバシー変数であることを意味します。
更に,biggerFont変数もフォントサイズを大きくするために定義しておきます。

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _biggerFont = const TextStyle(fontSize: 18.0);
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return (new Text(wordPair.asPascalCase));
  }
}

次に,_buildSuggestions()関数を追加します。このメソッドは提案されたワードを生成するリストビューを作成する関数となります。
ListViewクラスはビルダープロパティitemBuilder(ファクトリビルダーとコールバック関数)
BuildContextとイテレータ i がこの関数に渡されるパラメータとなります。イテレータは0から始まり,ワードを提案する度に関数が呼ばれてインクリメントされます。

  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // itemBuilderのコールバックはワードが提案される度に呼ばれ,ListTitleの行にそれを配置する
      itemBuilder: (context, i) {
        if (i.isOdd) return new Divider();
        final index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }
}

このままでは動きません。なぜなら,_buildSuggestions関数は_buildRowをワードペア毎に読んでいるからです。この関数は,リストタイトルに次のステップで注目する行生成のための関数で,新しいペアを表示する役割を担います。

_buildRow関数は,以下のように実装されます。

class RandomWordsState extends State<RandomWords> {
  ...

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

そして,RandomWordsStateを以下のように変更します。

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _biggerFont = const TextStyle(fontSize: 18.0);
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );
  }
  ...

}

最後に,MyAppクラスを変更します。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new RandomWords(),
    );
  }
}

これで,ユーザがスクロールすると自動でワードを生成して表示するリストビューが完成です。

Step5: インタラクティブなアプリを作成する

次に,各行にお気に入りボタンを追加します。そのためには,ユーザがタップできるようにしないといけませんね。

まず,RandomWordsStateに_savedセットを追加します。

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _saved = new Set<WordPair>();

  final _biggerFont = const TextStyle(fontSize: 18.0);
 ...

}

次に,_buildRow関数にお気に入りに追加されているかを確認するためのalreadySavedを追加し,ハート型のアイコンをリストに表示します。

class RandomWordsState extends State<RandomWords> {
  ...

  Widget _buildRow(WordPair pair) {
    final alreadySaved = _saved.contains(pair);
    return new ListTile(
      title: new Text(
        pair.asPascalCase, style: _biggerFont,
      ),
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
    );
}

ここまででリロードすると,リストの行の右端にハート型のアイコンが表示されているはずです。

最後に,タップできるように_buildRow関数を変更します。
buildRow関数の完成形は以下。

Widget _buildRow(WordPair pair) {
    final alreadySaved = _saved.contains(pair);
    return new ListTile(
      title: new Text(
        pair.asPascalCase, style: _biggerFont,
      ),
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      onTap: () {
        setState(() {
           if (alreadySaved) {
             _saved.remove(pair);
           } else {
             _saved.add(pair);
           }
        });
      },
    );
  }

Step6: 新しいScreenに誘導する

前のステップでお気に入りボタンを追加したので,このステップではお気に入りリストの画面を追加して,画面遷移をやってみます。

Flutterでは,Navigatorが画面遷移はアプリのルートを含むstackを管理します。

  • Navigatorのスタックにルートがプッシュされると,そのルートに画面が更新される
  • NavigatorのSタックからポップされると,前のルートに戻る

では,実際に追加して遷移処理してみます。
まず,RandomWordsStateのbuildメソッドでAppBarにリストアイコンを追加します。ユーザがこれをクリックすると,お気に入りが追加要素をもった新しいルートがNavigatorにプッシュされ,アイコンを表示します。

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        actions: <Widget>[
          new IconButton(icon: new Icond(Icons.list), onPressed: _pushSaved)
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

次に,RandomWordsStateクラスに_pushSaved()関数を追加します。

class RandomWordsState extends State<RandomWords> {
  ...
  void _pushSaved() {}
}

ここでアプリをホットリロードしてみると,AppBarにリストアイコンが追加されていることがわかります。

せっかくアイコンを追加したので,タップしたときの処理を追加します。
ユーザがAppBarのリストアイコンをタップした際に,ルートを生成してNavigatorスタックにプッシュします。このアクションで,新しいルートを表示して画面を変更します。

新しいページの内容は,無名関数MaterialPageRouteのbuilderプロパティでビルドされます。

下のように,NavigatorのスタックにルートをプッシュするNavigator.pushを追加し,MaterialPageRouteとそのビルダーを追加します。ここでは,ListTileの行を生成するコードを追加しておきます。
ListTileのdivideTiles()メソッドが各ListTileの間に水平のスペースを入れてくれます。
devided変数が最後の行を保持していて,これは簡易関数toList()によってリストに変換されます。

  void _pushSaved() {
    Navigator.of(context).push(
      new MaterialPageRoute(
          builder: (context) {
            final tiles = _saved.map(
                (pair) {
                  return new ListTile(
                    title: new Text(
                      pair.asPascalCase,
                      style: _biggerFont,
                    ),
                  );
                }
            );
            final divided = ListTile.divideTiles(
                tiles: tiles,
                context: context
            ).toList();
          })
    );
  }

ビルダープロパティはScaffoldを返します。Scaffoldは新しいルートのためのAppBar,"SavedSuggestions"を持っています。新しいルートのbodyはListTiles行を含むListViewで構成されていて,各行はdividerによって分割されています。

void _pushSaved() {
    Navigator.of(context).push(
      new MaterialPageRoute(
          builder: (context) {
            final tiles = _saved.map(
                (pair) {
                  return new ListTile(
                    title: new Text(
                      pair.asPascalCase,
                      style: _biggerFont,
                    ),
                  );
                }
            );
            final divided = ListTile.divideTiles(
                tiles: tiles,
                context: context
            ).toList();

            return new Scaffold(
                appBar: new AppBar(
                  title: new Text('Saved Suggestions'),
                ),
                body: new ListView(children: divided)
            );
          }
      )
    );
  }

ホットリロードし,適当にお気に入りを追加して画面遷移すると,新しい画面が表示されるはずです。ちなみに,return new Scaffold以下を書かないと,次の画面が表示されず真っ黒な画面が表示されます。

ちなみに,前の画面に戻るためにわざわざNavigator.popを実装する必要はありません。勝手に新しい画面のAppBarに自動で"戻る"ボタンが追加されているはずです。

Step7: テーマを使ってUIを変更する

最後のステップですが,アプリのテーマで遊んでみます。

アプリのテーマはThemeDataクラスをいじることで簡単に変更することができます。今はデフォルトテーマを使っているので,白をベースとした色に変更してみます。

MyAppを次のように変更します。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      theme: new ThemeData(
        primaryColor: Colors.amber
      ),
      home: new RandomWords(),
    );
  }
}

ホットリロードすると色がかわることがわかります。

MaterialライブラリのColorsクラスはたくさんの色の定数を提供していますので,いろんな色に変更して遊んでみると良いと思います。ホットリロードしながら変更すると簡単に確認しながら変更できます。

終わりに

ここまでやった感想としては,Flutterは非常によく作られてるなと思いました。
dartも始めてでも直感的でわかりやすいし,学習コストは低そうという印象。
本気でいろいろやり始めると壁があるのかもしれませんが,個人的にはやってみる価値はあるかなという感じです。

46
41
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
46
41