ネイティブは年単位での経験が有るわけではなく、フロントエンドとバックエンドをメインで経験しています。そんな中でFlutterをそれなりに使えるようになるまで残したメモをまとめています。
ホットリロードとDartのおかげで、
個人的には「これ、ネイティブ・・?」と錯覚するほどフロントエンドっぽさを感じました。
※なにか変な部分や間違いが有りましたらご指摘いただけると幸いです!
環境構築
エディタは何を使えばいいんだろう?
個人的にはVSCodeがなれていたのでそちらで開発していましたが、やりたいなと思ったことは今のところできています。
環境構築する
公式のget-started通りにやればokです(丸投げだけど本当に思っていたよりかなり楽に構築できます)。
途中なにかが不足している場合などでも、flutter doctor
コマンドで環境構築完了に何が足りないかわかるのがとてもありがたい。
参考: https://flutter.dev/docs/get-started/install
ビルドする
ネイティブ経験がそれほどなかったので、「え、ビルド、大変そう・・」と思ったのですがかんたんでした。
シミュレータかmacにつなげた実機か、どのデバイスにビルドするか選択してビルドします。
実機の場合は少し設定が必要があるかと思うので「ios実機ビルドできない」の項目を下記参照ください。
コマンドの場合
$ flutter devices
---
2 connected devices:
hogePhone • XXXXXXXXXXXXXXXXXxx • ios • iOS 13.4.1
iPhone 11 Pro Max • XXXXXXXXXXXXXXXXXxx • ios •
$ flutter run -d {device_id}
VSCodeでビルドする場合
vscodeの画面右下にビルド対象が表示されているので対象にしたい端末を設定します。
よく使ったコマンド
$ flutter create {app_name}
# pubspec.yamlを更新したときパッケージを取得(基本エディタで保存したときに自動で取得される)
$ flutter pub get
# ビルド
$ flutter run
$ flutter run -d {device_id} # デバイスを指定してビルド
# deviceIdを列挙
$ flutter devices
-----------------
2 connected devices:
hogePhone • 00008030-001838320CF2802E • ios • iOS 13.4.1
iPhone 11 Pro Max • 9A5B8A02-0CE1-4955-9450-B787F6BAD274 • ios •
packageの追加
pubspec.yamlに追加したいpackageを追記して保存 -> 自動でinstallが始まる
dependencies:
flutter:
sdk: flutter
http: ^0.12.1 # ←
HotReloadとHotRestart
HotReload
エディタでビルドを実行した場合は保存した際にホットリロードされますが、
ターミナルの方で flutter runでビルドした場合、
コマンドでr
をいちいち入力しないとホットリロードされません。(ほんとに?!)
$ flutter run
....
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
HotRestart
コンパイルエラーやアプリが落ちた場合やパッケージ追加時・フォントの変更など、ホットリロードしても変更が反映されないケース(下記参考に詳細)があります。なので、更新されないなぁという時はホットリスタートを行う必要があります。(それでも変更されないケースがあるのでその時はアプリ再起動)
下記はVSCodeのデバッグUI。緑の矢印がhot-restart
参考: https://flutter.dev/docs/development/tools/hot-reload#special-cases![uploading-0]()
自動フォーマット
VSCodeの場合settings.jsonに下記追加
フォーマットはされるけど変なフォーマットされるなぁと言う時は、大体,
が無い。
自分は一行にまとめたいケースなどではあえて,
を省略したりしていました。
"editor.formatOnSave": true,
状態管理
自分は一番これに時間を費やしてしまいました。
setState, BLoC, Provider, Reduxなど、状態管理のやりかたが色いろあるようで、ちょこちょこ変わっているそうです。
僕はめんどくさがりなので、今後メインになりそうな手法をとりあえず選びたい・・と思っているものの
“What toppings should I get on my pizza?”
参考: https://blog.codemagic.io/flutter-tutorial-pros-and-cons-of-state-management-approaches/
みたいなもんだと書いてあるとおりどれがいいっていうのはまだ明確にはなっていないそう。。
というのを横目で見つつgoogleとしては、特にこだわりが無ければProviderを推すということなのでProviderを使ってみることにしました。
Providerで状態管理
テキストで実装の流れを書くと下記のような感じ
- rootにしたいwidgetにChangeNotifierProvider でwidgetをwrapする
- Providerには状態と各methodをもたせる
- rootに設定したtree内でProviderを参照できる
import 'package:provider/provider.dart';
import './providers/counter.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext ctx) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => Counter()),
// 複数設置できる
],
...
import 'package:provider/provider.dart';
class Counter with ChangeNotifier {
int count = 0;
void add() {
count++;
notifyListeners(); // これでproviderに更新を伝える
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context); // CounterのProviderを参照
return Column(
children: <Widget>[
Center(child: Text(counter.count)), // Providerに持たせている状態を参照
FlatButton(
onPressed: () => counter.add(), // Providerが持っているmethodを呼び出す
child: Text('Plus One'),
),
],
}),
);
}
}
REST-APIを使う
まずモデルを作成する
class AlbumModel {
final int userId;
final int id;
final String title;
AlbumModel({this.userId, this.id, this.title});
factory AlbumModel.fromJson(Map<String, dynamic> json) {
return AlbumModel(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
そのModelを使ってapiを叩いていく
GET
Future<AlbumModel> fetchAlbum() async {
String url = 'https://jsonplaceholder.typicode.com/albums/2';
final response = await http.get(url);
if(response.statusCode == 200) {
return AlbumModel.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load album');
}
}
POST
Future<AlbumModel> createAlbum(String title) async {
String url = 'https://jsonplaceholder.typicode.com/albums';
final http.Response response = await http.post(
url,
headers: <String, String> {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, String>{
'title': title,
})
);
if (response.statusCode == 201) {
return AlbumModel.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load album.');
}
}