Flutter tutorialで簡単なアプリを作る
Flutterの環境構築が終わったので、早速Tutorialをやっていきます。
記事はVSCodeのていで書いているので、他のアプリの方は適宜読み替えをどうぞ😊
お題は'Startup Name Generator'。要件としてはこんな感じ。
- スタートアップ企業の名前が並ぶ
- ユーザは名前を選択/選択解除して一番いい名前を選べる
- 非同期的に名前を生成
- スクロールされるとさらに名前を生成&スクロールは限界なくできる
このパートで学べること
- iOSとAndroidで自然に見えるFlutter appの書き方
- Flutter appの基本構造
- パッケージを探して見つけて拡張する方法
- ホットリロードの使い方
- ステートを持ったウィジェットの作り方
- 終わりなく非同期的に読み込まれるリストの作り方
Step1. Flutter appをつくる
これは環境構築の中でやったところ。
$ flutter create hello_flutter
lib/main.dart
を主に編集していくよ、とのこと。ふむふむ。
lib/main.dart
をすべて消して下記のコードに書き換え
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'), ), ), ); } }
(Dartのシンタックスハイライトきくんですね。はてなさんすごい…!)
Tipsとして、インデントがくずれた場合は「Alt+Shift+F」で整形できるよ、と紹介されています。至れり尽くせり…!
2. エミュレータを起動してホットリロードを確認
「F5」でエミュレータを起動、「Shift+Cmd+F5」でホットリロードが行えます。
(アプリ実行中は保存でもホットリロードできるとのことなのだけど、うまくいかない…これも後回し案件です📝)
さて、「Hello, World」の文字が表示されました。わーい!😊
Step2. 外部パッケージを使う
名前を生成する為にenglish_wordsという外部ライブラリを導入します!
外部ライブラリはPub siteからDLできるよ、とのこと。RubyでいうRubyGemsみたいな感じでしょうか。
pubspec.yaml
にenglish_words
を加える
これがGemfile的なものっぽい。
dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 english_words: ^3.1.0
2. flutter packages get
の実行
とあるのですが、保存したら勝手に走った…!!!
あまりに早すぎて(2.3s)本当にちゃんとgetできたの?と心配になりますが、pubspec.lock
(Gemfile.lock的なものっぽい)を見るとenglish_wordsの項目が増えているので追加できているようです!
3. packageのimport
lib/main.dart
でenglish_words
をimportします。
import 'package:english_words/english_words.dart'
4. English Wordsパッケージを使って文字列を表示する
Hello, World
の代わりに今importしたパッケージを使ってランダムに生成した文字列を表示します。
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), ), ), ); }
ホットリロードすると、(保存しなくても画面更新するだけで)ランダムな文字列が表示されるようになりました!
Step3. Statefulなウィジェットを加える
statelessなウィジェットは不変で、プロパティを変更できない。すべての値がfinalとなります。
statefulなウィジェットはウィジェットのライフタイム中変化する可能性のあるstateを保持します。
statefulなウィジェットの実装には最低でも2つのclassが必要になります:
- インスタンスを作るStatefulWidgetクラス
- Stateクラス
StatefuleWidgetクラスはimmutable(不変)ですが、Stateクラスはウィジェットのライフタイムの間中持続します(?)
RandomWords
というstatefulなウィジェットを加えると、RandomWordsState
というStateクラスが作られます。
すると今つくっているMyApp
のstatelessウィジェットの子としてRandomWords
を使えるようになるよ、とのこと。
読むだけじゃよくわからないのでやってみます。
lib/main.dart
の最下部に下記を追記
class RandomWordsState extends State<RandomWords> { // TODO Add build() method }
Stateクラスがビルドメソッドを持たなくてIDEに怒られるけど、いったん放置。
2. statefulなRandomWords
ウィジェットをlib/main.dart
に追記
RandomWords
ウィジェットはこのRandomWords
のStateクラスを作る以外はほとんど何もしないとのこと。ふむふむ。immutableだからですかね。
class RandomWords extends StatefulWidget { @override RandomWordsState createState() => new RandomWordsState(); }
3. 1で放置していたStateクラスにビルドメソッドを追加
class RandomWordsState extends State<RandomWords> { @override Widget build(BuildContext context) { final wordPair = WordPair.random(); return Text(wordPair.asPascalCase); } }
4. MyAppクラスからStep2で追加したコードを削除してRandomWords()
を呼ぶ
Widget build(BuildContext context) { return MaterialApp( title: 'Welcome to Flutter', home: Scaffold( appBar: AppBar( title: Text('Welcome to Flutter'), ), body: Center( child: RandomWords(), ), ), ); }
5. アプリをリロード(ホットリロード)するとStep2と同じ挙動をします!
Step4. ずーっとスクロールできるListViewをつくる
最終段階!
RandomWordsState
クラスを拡張して、ユーザがスクロールすると永遠にリストが表示されるListViewウィジェットを作ります。
ListViewのビルダーファクトリーconstructorを使うとListViewを非同期的に作成できるそうです。
_suggestions
リスト、_biggerFont
変数をRandomWordsState
クラスに追加
class RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _biggerFont = const TextStyle(fontSize: 18.0); // }
Javaと違ってDart言語はpublicやprivateといった予約語を持たなくて、_
をつけるとprivateになるそうです。A Tour of the Dart Language - Dart
2. _buildSuggestions()
functionをRandomWordsState
クラスに追加
Widget _buildSuggestions() { return ListView.builder( padding: const 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
コールバックは単語のペアを呼び出してListTile
行に配置します。偶数行にはListTile
行を、奇数列にはセパレータを配置します。/*2*/
/*1*/
で触れたセパレータがこれですね。/*3*/
i ~/ 2
はRubyでいうところのi/2
((i/2).to_i)
と同。
単語のペア数からセパレータ数を引いた数を計算するのに使います。/*4*/
単語のペアが終端に達したら、さらに10個作ってsuggestions listに加えます。
3. _buildRow
functionをRandomWordsState
に追加
これはwordPairとstyleを持ったListTile
を作るだけ。
Widget _buildRow(WordPair wordPair) { return ListTile( title: Text( wordPair.asPascalCase, style: _biggerFont, ), ); }
4. _buildSuggestions()
を呼ぶようbuild()
を修正
RandomWordsState
クラスで直接english_wordsライブラリを呼ぶのではなく、build()
内で_buildSuggestions()
を呼ぶようにします。
ふむふむ。ちょっとわかってきました。
Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Startup Name Generator'), ), body:_buildSuggestions(), ); }
5. MyApp
クラスのtitle表示とhome(bodyみたいな感じ?)を修正してRandomWords()を呼び出すよう修正
Widget build(BuildContext context) { return MaterialApp( title: 'Startup Name Generator', home: RandomWords(), ); }
6. ホットリロードすると、永遠にスクロールできるStartup Name Generatorの完成です!!!