入門!アトミックデザインに行ってきました
今日は@nrsさんのAtomicDesign入門に行ってきました!
振り返りと理解の確認のためにまとめを書かせて頂きます。
登壇資料はこちら。
AtomicDesignとは
モジュール分割を基本としたデザインシステム(デザインの指針・デザインの共通言語)のこと。
モジュールは粒度によって下記の5つのステージに分けられる。
- Atoms 原子: 最小のコンポーネント。ex. Logo, TextBox
- Molecules 分子: ふたつ以上のAtomsを組み合わせたコンポーネント。
ex. HeaderLink(Logo + HeaderTitle), SearchBox(TextBox + PrimaryButton) - Organisms 生物: AtomsとMoleculesを組み合わせたコンポーネント。
ex. Header(HeaderLink + SearchBox) - Templates ページの基礎となるコンテンツ構造、ワイヤーフレームのこと。
ex.Header + BreadCrampList + Contents - Pages テンプレートにデータを流し込んだもの
※ Templatesから「原子どこ行った??」状態なのは、Atom〜Organismsまではエンジニア/デザイナー向け、Templates以上は上流のお歴々と話をする為の道具となるからじゃないでしょうか、とのこと。
※ Atomとは、「UIとしての機能性を破壊しない最小要素」。
もしButtonは矩形とラベルにわけると、ラベルは押せないのでButtonとしての機能を失う。
だから、Buttonが最小要素(Atom)。
なるほどわかりやすい!
けどやっぱり、そうシンプルな話でもない。
AtomicDesignは5階層に分けることでモジュールの乱立を防ぐことができることが重要なのだけど、いつでもAtom -> Molecules -> Organismsと分けられるわけではない。
たとえば、こんなページがあったとすると
Atoms: TableCell, PagingButton Molecules: TableRow(TableCellの集まり), PagingMenu(PagingButtonの集まり) Atoms: TabButton, TabContent Molecules: TabMenu(TabButtonの集まり)
Organismはこうなる
Organisms: Table(TableRow + PagingMenu), Tab(TabMenu + TabContent)
でも、Organism同士であるTableとTabを足しても、これはTemplateとは言えない。
(HeaderとかFooterとかSideMenuとか足りない)
…おや?🤔
AtomicDesignは使えないのか?
わけではない。
AtomicDesignはデザインシステムである。
AtomicDesignの目的
- ❌AtomicDesignに沿って作ること
- ⭕デザイナーとエンジニアの協業のための共通基盤づくり
-> 共通認識のもとで会話ができるようになること!
コンポーネント指向
すべてはコンポーネントであるという考え方
Webだと、image
, textbox
, button
etc, ぜんぶ組み合わせで作れてしまう。
ゲームやネイティブアプリだと、全てコンポーネントを作る(必要がある)。
AtomicDesignとは粒度にルールを加えたコンポーネント指向のこと。
たとえばよくあるこんなケース
デザイナー💁「モーダルに『閉じる』ボタンをつけたい!」
コンポーネント化していないと
エンジニア「いくつあるのかわからないモーダル、全部修正してテストするのか😇」
コンポーネント化していると
エンジニア「Modalコンポーネントにつければいけるぜ😋」
AtomicDesignのメリット: 変更に強くなる
AtomicDesign実践入門
どこからつくる?🤔
- サンプルページを作る
デザイナー💁「こんな感じにしよっか」 - Atomの切り出しの検討
- 組み合わせてMoleculesを作る
- 組みあわせてOrganismsを作る
2〜4の工程をデザイナーとエンジニアを一緒にやれたらベスト!!
(おすすめはホワイトボードだけど、Web上でやったりとかでもOK) - 4まで共通認識が出揃ったら各デザイナー、エンジニアが実装
※ プロパティやイベントを持った最小要素(Atoms)を仮実装->Molecules->Organismsと進める
(下位要素があれば上位要素もそれで作れるから)
※ 同じレイヤーの作業は別の人と重複することがあるので、作業分担は最初に要素を洗い出すことが大事 - Template
Templateのコード化は必須ではないが、もしするなら全体のTemplateとコンテンツのTemplateで分けるのがおすすめ - Organismsの粒度
さっきの例のように、Atom -> Molecules -> Organismの三段階では足りなくなるパターンがある。
→ OrganismはOrganismをコンポジションする
つまり、Organism + Organism = Organismもアリ
AtomicDesignは5階層であることに意味があるけれど、忠実にすることが難しさにつながるならその本質を見極めて取捨選択するべき。
AtomicDesignは守ることを目的とした不変の法律ではなく、開発に関わる全ての人達の指標。
目的はデザイナーとエンジニアの協業のための共通基盤づくり。
指標があるとお互いがお互いをリスペクトして仕事しやすくなる。
まとめ
- AtomicDesignとは
粒度にルールを加えたコンポーネント指向のこと - AtomicDesignのメリット
変更に強くなる、保守性があがること - AtomicDesignの目的
デザイナーとエンジニアが共通認識のもとで会話・協業ができるようになること
デザイナー💁「これに従ってデザインするね」
エンジニア😍「やりやすい!」
こんな会話が生まれるためのデザインシステム、それがAtomicDesign。
AtomicDesignとは、を学ばせて頂くと同時に、終始「よりよくするには?」を根底に感じる1時間でした。
ありがとうございました!!
参考: 原典AtomicDesign
Flutterでgoogleにログインする
毎日Flutter。
今日はFirebaseで認証をします。
Pubはこれ(firebase_auth)ですね。
google_sign_inを追加する
まずはgoogle_sign_inを追加します。
pubspec.yaml
に下記追記して、flutter packages get
。
dependencies: flutter: sdk: flutter image_picker: google_sign_in: ^4.0.1+1 # new! firebase_storage:
main.dart
にimport
を追記します。
import 'package:google_sign_in/google_sign_in.dart';
firebase_authを追加する
build.gradle
にclasspathを追記するのは前回やったので省略。
pubspec.yaml
とmain.dart
にそれぞれ下記追記します。pubspec
変更後はflutter packages get
。
dependencies: flutter: sdk: flutter image_picker: google_sign_in: ^4.0.1+1 firebase_auth: # new! firebase_storage:
import 'package:firebase_auth/firebase_auth.dart';
ログイン画面の追加
main.dart
に下記を追記し、MyApp
クラスからMyGoogleLoginPage()
を呼び出すように変更します。
class MyGoogleLoginPage extends StatefulWidget { @override _MyGoogleLoginPageState createState() => _MyGoogleLoginPageState(); } class _MyGoogleLoginPageState extends State<MyGoogleLoginPage>{ final GoogleSignIn _googleSignIn = GoogleSignIn(); final FirebaseAuth _auth = FirebaseAuth.instance; Future<FirebaseUser> _handleSignIn() async { final GoogleSignInAccount googleUser = await _googleSignIn.signIn(); final GoogleSignInAuthentication googleAuth = await googleUser.authentication; final AuthCredential credential = GoogleAuthProvider.getCredential( accessToken: googleAuth.accessToken, idToken: googleAuth.idToken, ); final FirebaseUser user = await _auth.signInWithCredential(credential); print("signed in " + user.displayName); return user; } @override Widget build(BuildContext context){ return Scaffold( appBar: AppBar( title: Text('none'), ), body: Center( child: Column( children: <Widget>[ SizedBox(height: 24.0), FlatButton( child: Text('SignIn'), onPressed: () => _handleSignIn() .then((FirebaseUser user) => print(user)) .catchError((e) => print(e)) ), ], ), ), ); } }
googleにログインができるようになりました!
FlutterでFirebaseのStorageを使う〜後編〜
昨日(FlutterでFirebaseのStorageを使う〜前編〜)の続きです。
AndroidXに対応する
さて、昨日、Firebaseを使うようbuild.gradle
に設定を加えたところ、大量のエラーが出てビルドができなくなりました😨
エラーメッセージ中に「ここ見てね!」とリンクが記述されているので、素直に見に行きます。
ざっくりいうと、android.supportというライブラリが非推奨になって、AndroidXってライブラリに変更されたよ!とのこと。
migrateの方法なども記載されているのですが、自動でAndroidX化してくれる方法を載せてくれている人がいたのでその方法で対応します。
# android/gradle.propertiesに下記を追加 android.useAndroidX = true android.enableJetifier = true
無事ビルドが通るようになりました!
firebase_storageをインポート
Pubでそれっぽいものを探すと、firebase_storageが出てきます。これだ!
image_pickerと違ってExampleは書かれていないので、手探りでやります💪
まずはpubspec.yaml
にfirebase_storage
を追加。
dependencies: flutter: sdk: flutter image_picker: firebase_storage: # new!
main.dart
でインポートします。
import 'package:firebase_storage/firebase_storage.dart';
Flutter製チャットアプリを支える技術を参考にuploadImage
メソッドを書きます。
(格好悪いけど、直接getImageから呼び出す)
class _MyHomePageState extends State<MyHomePage> { Future getImage() async { // (略) uploadImage(image); } Future<String> uploadImage(File file) async { int timestamp = DateTime.now().millisecondsSinceEpoch; String subDirectoryName = 'images'; final StorageReference ref = FirebaseStorage() .ref() .child(subDirectoryName) .child('${timestamp}'); final StorageUploadTask uploadTask = ref.putFile( file, StorageMetadata( contentType: "image/jpeg", )); StorageTaskSnapshot snapshot = await uploadTask.onComplete; if (snapshot.error == null) { return await snapshot.ref.getDownloadURL(); } else { return 'Something goes wrong'; } } // (略) }
※ 実行の際にMissingPluginException(No implementation found for ...)
というエラーがでる場合は、flutter clean & flutter packages get & flutter run
でうまくいく場合があります。
さて、実行すると、firebase-auth is not linked
、no auth token for request
といったメッセージが表示されますが、画像のアップロードに関してのエラーはないみたい!
わくわくしながらFirebaseのStorageを見てみると、タイムスタンプの名前のついた画像がアップロードされました!🎉
FlutterでFirebaseのStorageを使う〜前編〜
先日Flutterアプリにカメラボタンをつけましたが、写真を撮ったその後の処理がなかったので、今日は撮った写真をFirebaseにつないでアップロードしてみます。
ちなみにFirebaseとは、Googleが開発・提供しているmobile Backend as as Service(mBaaS)
のこと。
通常Webアプリケーションを作る際にはサーバを立てたりAWSのようなサービスを利用してバックエンドの処理を行いますが、それをサーバレスで実現する仕組みのこと(だと思う)。
認証、画像のストレージ、DB、プッシュ通知やSMSメッセージの送信など、様々なサービスが提供されています。
今回はこの画像ストレージに画像を送信します。
Firebaseにログイン、プロジェクトの作成
Firebaseにログインします。持っていない場合はgoogleアカウントを使って作成します。
「コンソール」をクリックすると、「Firebaseへようこそ」メッセージが表示されます。
「+ プロジェクトを追加」カードをクリック。
プロジェクト名を求められるので適当に入力、プロジェクトIDと地域を必要に応じて変更します。
アプリにFirebaseを追加
今作成したプロジェクトを選択します。
iOS、Android、Webが選択できる。ひとまずAndroidを選択します。
パッケージ名が求められるので、android/app/build.gradle
にあるapplicationId
を入力して「アプリを登録」ボタンを押下。
google-services.json
をダウンロードボタンが表示されるのでクリックしてダウンロード。android/app
ディレクトリに配置します。
android/build.gradle
とandroid/app/build.gradle
にそれぞれ表示されているコードをコピーします。
※ google-services.json
にはAPIキーが記載されているので、そのままgit pushしないよう注意。
やってしまいGitGurdianからワーニングがきたうっかりさんはわたしです。
Storageの設定変更
左ペインの「Storage」をクリック、「ルール」タブをクリックします。
今回認証は行わないので、誰でもread&writeできるようにルールを変更します。
service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow read, write; } } }
というところで、続きはまた明日やっていきます。
Dart言語の3つのコンストラクタ
少しずつFlutterに慣れてきたところで、Dart言語も少しお勉強します。
今日はコンストラクタについて。
DartPadというブラウザ上で実行できるREPLがあるので、入力しながら読むと理解がはかどりました。
なお、今日の参考ページはこちら -> Dartのコンストラクタについて - DevelopersIO
クラスの構成
整理すると難しくないのですが、慣れるまで違和感があったので一度整理します。
class Apple { static final String nickname = 'Ann'; // staticをつけるとクラス変数になる String color; // staticをつけないとインスタンス変数。クラス変数と同名は指定できない Person() { // class名()でコンストラクタを記述 this.color = 'Red'; // this.変数名(メソッド名も同様)でメンバにアクセス } }
変数の宣言にはvar, const, finalも使えるし、String, intといった型も指定できます。
ただどちらかは指定しないとコンパイルエラーになるみたい。ふむ。
生成的コンストラクタ(Generative Constructors)
いわゆるふつうのコンストラクタ。
class Person { String name; Person() { this.name = 'Bob'; } } void main() { Person foo = new Person(); print(foo.name); // => 'Bob' }
もちろん引数も取れます。
class Person { String name; Person(String name) { this.name = name; } } void main() { Person foo = new Person('Alice'); print(foo.name); // => 'Alice' }
Automatic field initialization
冗長な感じがするので、代入だけなら簡略して記述できます。
class Person { String name; Person(this.name); } void main() { Person foo = new Person('Tom'); print(foo.name); // => 'Tom' }
複数の引数でもOK。
class Person { String name; int age; Person(this.name, this.age); } void main() { Person foo = new Person('Tommy', 20); print(foo.name); // => 'Tommy' print(foo.age); // => 20 }
Named Constructors
コンストラクタのオーバーロードはできないそうです。
代わりに、コンストラクタに任意の名前を付けて通常のコンストラクタから呼び出す方法を取るみたい。
class Person { String name; String full_name; Person(){ this.name = 'Tommy'; } Person.full_name(){ this.full_name = '${this.name} Smith'; } } void main() { Person foo = new Person(); print(foo.name); // => 'Tommy' print(foo.full_name); // => null Person bar = new Person.full_name(); bar.name = 'Jacob'; print(bar.name); // => 'Jacob' print(bar.full_name); // => 'null Smith' }
うん…?コンパイルは通るけど、こういう使い方じゃない気がする🤔
Redirecting Constructors
オーバーライドしたい時はこれで書くのがよさそう。
class Person { String name; String full_name; Person() : this.name(); Person.name() { this.name = 'William'; this.full_name = '${this.name} Smith'; } } void main(){ Person foo = new Person(); print(foo.name); // => 'William' print(foo.full_name); // => 'William Smith' }
参考ページにはリダイレクト元(左辺)のコンストラクタのボディで処理もできる、とあるのだけど、実行すると「Redirecting constructors can't have a body.」と言われる。
仕様がかわったのかな…?
Initializer Lists
Named Constructorsで、コロンに続けてフィールドの初期化処理を記述できる。
複数フィールドの初期化時はカンマ区切りでリストアップできる。
class Person { String name; String full_name; Person() : this.full_name(); Person.full_name() : this.name = 'Ethan', this.full_name = 'Ethan Smith'; // field initializerの中からthisにアクセスはできない // これはコンパイルエラー // Person.full_name() : this.name = 'Ethan', // this.full_name = '${this.name} Smith'; } void main() { Person foo = new Person(); print(foo.name); // => 'Ethan' print(foo.full_name); // => 'Ethan Smith' }
親のコンストラクタを呼び出す時はリダイレクトコンストラクタを使う。
class Person { String name; String address; Person(); Person.name_address(name, address){ this.name = name; this.address = address; } } class Customer extends Person { Customer() : super.name_address('Emily', 'Tokyo'); // コンストラクタのボディで親クラスのコンストラクタは呼び出せない // Customer(String name, String address){ // super.name_address('Emily', 'Tokyo'); // } } main() { Customer foo = new Customer(); print(foo.name); // 'Emily' print(foo.address); // 'Tokyo' }
ファクトリ(factory)
factory
コンストラクタ。
初めて聞いたのですが、インスタンスを生成しないコンストラクタなので、自分でインスタンスを生成します。
singletonパターンの実装なんかに使うみたいです(あまりよくわかってない)。
class Person { String name; static var _instance; // 返すインスタンスはクラス変数にする factory Person(String name){ if (_instance == null) { _instance = new Person._internal(name); } return _instance; } Person._internal(this.name); } void main(){ Person foo = new Person('Alan'); Person bar = new Person('Elen'); print(foo.name); // => 'Alan' print(bar.name); // => 'Alan' print(foo == bar); // => true、同じインスタンスを返している }
定数コンストラクタ(Constant Constructors)
コンパイル時に定数オブジェクトをインスタンス化したい場合に使うそう。
グローバルスコープに定数オブジェクトを定義したい時、みたいな感じ…?🤔
定数オブジェクトのフィールドはfinal
、コンストラクタはconst
で定義、インスタンスの作成もconst
で行います。
ちなみにconst
とfinal
はどちらも定数を宣言するキーワードっぽいのだけど、以下のような違いがあるそうです。
final
: 代入が1度(宣言時)のみ、値が参照値の場合も変更不可const
はコンパイル時に値が決まる定数(暗黙的にfinalとなる)
Final and const - A Tour of the Dart Language
class Person { final name; const Person(this.name); } final foo = const Person('Colin'); void main(){ final Person bar = new Person('Mike'); print(foo.name); // => Colin print(bar.name); // => Mike }
以上、コンストラクタのまとめでした!
Flutterアプリにimage_pickerを使ってカメラ呼び出しボタンをつける
心はRubyistなのですが、毎日Flutterを触っているので今日も今日とてFlutterネタ。
Flutterといえば今日小田急線のロゴを見かけて、「Flutterのアイコンと似てる!!」とテンションがあがったのですが、帰って確認してみたら共通点は三角形ということと水色ということだけでした。
そんな大雑把な記憶を留める為に今日も小ネタを記してゆきます!
Flutterアプリにカメラ呼び出しボタンをつける
image_pickerというパッケージを使います。
手順としてはこんな感じ。
- image_pickerパッケージの読み込み
- image読み込みメソッド記述
- カメラ呼び出しアイコンを記述
順番にやっていきます。
image_pickerパッケージの読み込み
pubspec.yaml
に追記します。
dependencies: flutter: sdk: flutter image_picker: # => Added
バージョン指定はなくても構いません。
書いたらflutter packages get
でインストール。
なお、インストールしてるのにうまく反映されないな?てときはキャッシュが悪さしてることが往々にしてあるようなので、flutter clean
& flutter run
を行うと解決する場合があるようです。
image読み込みメソッドを記述
exampleがすごく充実してるのでその通りやるだけ。
main.dart
にimport
を記述します。
import 'package:image_picker/image_picker.dart'; import 'dart:io';
RandomWordsState
クラスにgetImage
メソッドを追記。
(前回の続きに追記しているのでRandomWordsクラスですが、example通りだと_MyHomePageState
クラス)
class RandomWordsState extends State<RandomWords> { // (略) File _image; Future getImage() async { var image = await ImagePicker.pickImage(source: ImageSource.camera); setState(() { _image = image; }); } // (略) }
カメラ呼び出しアイコンを記述
getImage
を呼び出す為のアイコンを追記します。
class RandomWordsState extends State<RandomWords> { // (略) @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Startup Name Generator'), ), body:_buildSuggestions(), floatingActionButton: FloatingActionButton( child: Icon(Icons.add_a_photo), onPressed: getImage, ), ); } // (略) }
これで、ボタンを押すとカメラアプリが呼び出されるようになりました!
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の完成です!!!