Android/iOS向けアプリ作りたいけどなんかいい開発環境ないかなぁ。
Electronのスマートフォン版あったら便利なんだけどなぁ。
Flutter?聞いたことはあるけどDartが不人気なことで有名だしなぁ。
(念の為改めて調査してみる)
は?Dartめっちゃいいじゃん!なにこのC言語系の正当進化版みたいな可読性!?何が不人気なの?? →完全にただの記憶違いでしたm(_ _)m
Flutterもデスクトップ・モバイル・Webをカバーした理想的な環境っぽいなぁ。
よし、まずは入門だ!
(最終更新:2020.09.27)
作業した環境
- OS:macOS Catalina バージョン10.15.7
集中講座の元動画様(英語)
Flutter Crash Course
この記事の位置づけとして、基本的に元動画様に沿って進めていって、補助として個人的に記録しておきたいと思った情報を日本語でわかりやすくざっくりまとめた感じです。
作るもの
シンプルな単語ペアジェネレータ。
ランダムに生成された単語ペアをリスト表示する。
気に入った単語ペアはタップで印をつけることができ、別ページで一覧を確認できる。
Flutterとは
Google製UIツールキット。
一つの共通化されたコードをもとにモバイル・Web・デスクトップ向けのネイティブクロスプラットフォームアプリが開発できる。
パフォーマンスが極めて高い。
Dartとは
Flutterが採用しているオブジェクト指向型プログラミング言語。
UIプログラミングに最適化されている。
すべてのプラットフォームで高速に動作する。
文法的には、例えるならJavaの要素を持つJavaScript。
Widgets
DartではすべてのパーツがWidgetである。
マテリアルデザインを採用している。
Scaffold(土台)、AppBar、Container、Image、Icon、などなど。
StatelessとStatefulがある。
build関数で作成する。
Stateless Widgets
状態を持たない静的なWidgetのこと。
実行中変化せず、自身を再描画する必要がないUIに使われる。
Statefull Widgets
状態を持っている動的なWidgetのこと。
実行中に変化し、自身を再描画する必要があるUIに使われる。
開発環境構築
ざっくりと今回必要な環境をまとめておきます。
-
Flutter本体
- Flutterから環境にあったFlutterをインストール(圧縮ファイルを解凍して任意の場所に配置)
- flutter/binにパスを通す
-
Visual Studio Code
- Visual Studio Codeからインストール
- Flutterエクステンションを追加
- Dartエクステンションを追加
-
Xcode
- AppStoreから最新のXcodeをインストール
- 下記コマンドを実行してXcodeを設定
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
-
Android Studio
- Android Studioから最新のAndroid Studioをインストール
- Flutterプラグインを追加
- 仮想デバイスを作成
開発環境構築チェック
テンプレートアプリを動かして開発環境が構築されたことを確認します。
※ここまでで詳細な手順やトラブルシューティングは省いているので、適宜自分で調べながらになるかと思います。
ターミナルで作業ディレクトリに移動して下記コマンドを実行
flutter create wordpair_generator
cd wordpair_generator
code .
★ターミナルからcode
コマンドが使えない場合は一度VSCodeを開いて、
Cmd+Shift+P
shell と入力
Shell Command: Install 'code' command in PATH を選択
とすると使えるようになる。
wordpair_generatorプロジェクトをVSCodeで開けたら、右下の「No Device」をクリックして「Start iOS Simulator」を選択し、シミュレータを立ち上げます。
上部のメニューから、
Run > Start Debugging > Dart & Flutter
と選択してデバッグ起動します。
テンプレートアプリが無事起動し、右下の+ボタンをクリックしたら画面中央の数字が増えていく、という挙動を確認できればOKです。
コーディング準備
コードを打ち込んで行く前に、いくつか準備しておきます。
私の場合、Dartのおすすめセッティングとかいうものを適用した結果、保存のたびに強制フォーマットされてめちゃくちゃやりにくかったのでコマンドパレットCmd+Shift+P
からopen settings
と打って設定ファイル(json)を開き、設定を一部OFFにしました。
また、動画とは順番が前後しますが、事前に依存パッケージをインストールしておくとスムーズかもしれません。
# 〜(省略)〜
dependencies:
flutter:
sdk: flutter
english_words: ^3.1.5 # この行を追加
# 〜(省略)〜
コーディング(main.dart)
エントリポイントのある、メインファイルです。
これだけだとまだ動きません。
import 'package:flutter/material.dart';
import './random_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.purple[900]),
home: RandomWords()
);
}
}
コーディング(random_words.dart)
画面の構成やランダムな単語ペアの生成・表示といったロジックを担当するファイルです。
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
class RandomWords extends StatefulWidget {
@override
RandomWordsState createState() => RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final _randomWordPairs = <WordPair>[];
final _savedWordPairs = Set<WordPair>();
Widget _buildList() {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemBuilder: (context, item) {
if (item.isOdd) {
return Divider();
}
final index = item ~/ 2;
if (index >= _randomWordPairs.length) {
_randomWordPairs.addAll(generateWordPairs().take(10));
}
return _buildRow(_randomWordPairs[index]);
}
);
}
Widget _buildRow(WordPair pair) {
final alreadySaved = _savedWordPairs.contains(pair);
return ListTile(
title: Text(pair.asPascalCase, style: TextStyle(fontSize: 18.0)),
trailing: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null),
onTap: () {
setState(() {
if (alreadySaved) {
_savedWordPairs.remove(pair);
}
else {
_savedWordPairs.add(pair);
}
});
}
);
}
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
final Iterable<ListTile> tiles = _savedWordPairs.map((WordPair pair) {
return ListTile(
title: Text(pair.asPascalCase, style: TextStyle(fontSize: 16.0))
);
});
final List<Widget> divided = ListTile.divideTiles(
context: context,
tiles: tiles
).toList();
return Scaffold(
appBar: AppBar(title: Text('Saved WordPairs')),
body: ListView(children: divided)
);
}
)
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WordPair Generator'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.list),
onPressed: _pushSaved)
]),
body: _buildList()
);
}
}
特に気になった文法
Dartの文法はJavaやJavaScriptの流れを組んだような、素直でわかりやすい文法だと感じていますが、初見で特に気になったりわからなかったものについてまとめます。
- 「~/」除算、整数(int)の結果を返す
- 「_」アンダースコア始まりの識別子でプライベートアクセス指定を表す
トラブルシューティング
- 実行したままコードをいじるとエラーが出て何も表示されなくなる
- 原因はわかりませんが、実行中のアプリを再起動すると解決しました。
あとがき
Dartについて、よく考えられた直感的な命名の各種機能がそろっていて、文法も素直で堅実な印象で書いていて楽しいと感じました。
ただ、UIに最適化された言語だからなのか、コンストラクタにコンストラクタをどんどん重ねて作っていくやりかたがやりやすいので、気をつけないとついつい"ダーティー"なコードを書いてしまいがちとも思いました。
そこがまたいいのかもしれませんね。